whats77 0.1.5
Facilitador para uso da API WhatsApp (ZAPI)
Requires Python: >=3.8

# Whats77

**Versão:** 1.0.0  
**Compatibilidade:** Retrocompatível com versões 0.1.x  
**Backend:** Whats77 Manager (FastAPI + Redis + Z-API)

---

## 📘 Visão Geral

O **Whats77** é um *facilitador* para o envio de mensagens via WhatsApp através da **Z-API**, agora totalmente integrado ao **Whats77 Manager** — um orquestrador que cuida de cadência, idempotência, retentativas automáticas e segurança via API Key.

Com esta nova integração, suas automações **não precisam mais chamar a Z-API diretamente**.  
Basta chamar os mesmos métodos:

- `send_text`
- `send_image`
- `send_audio`
- `send_document`

e o Manager fará todo o controle de fila e envio.

🆕 Além disso, o Whats77 agora suporta **envio estruturado em grupo** via:

- `send_group(...)` → usa o endpoint `/enqueue_group` do Manager, garantindo ordem e idempotência por grupo.

---

## 🧱 Arquitetura do Manager

```text
whats77_manager/
├── apps/
│   ├── manager_api.py     # FastAPI: /enqueue, /enqueue_group, /status, /health
│   ├── worker.py          # Worker: consome fila e dispara via Z-API
│   ├── rate_limiter.py    # Controle de cadência (Redis)
│   ├── scheduler.py       # Retentativas e backoff exponencial
│   ├── storage.py         # Idempotência, grupos e DLQ
│   ├── models.py          # Schemas (MessageJob, GroupMessageRequest, SendResult)
│   └── zapi_client/
│       ├── whats77.py     # Cliente HTTP direto na Z-API
│       └── senders.py     # Multi-instâncias Z-API
└── .env
````

O **Manager** atua como intermediário:

* ✅ Controla limites e cadência
* ✅ Evita envios duplicados (idempotência)
* ✅ Distribui mensagens entre múltiplas instâncias Z-API
* ✅ Tenta novamente em caso de falha temporária
* 🔒 Requer **API Key** (`X-API-Key`) para autenticação
* 📦 Garante **ordem e isolamento** em grupos de mensagens (`/enqueue_group`)

---

## ⚙️ Configuração

### Arquivo `.env`

Crie um arquivo `.env` na raiz do seu projeto:

```env
MANAGER_URL=http://localhost:8000
MANAGER_API_KEY=meu_token_super_seguro
SENDER_ID=0
```

> 🔁 Compatibilidade:
> Se as variáveis acima não existirem, o código tenta usar:
> `INSTANCE_ID`, `TOKEN`, `SECURITY_TOKEN` (modo legado).

---

### Configuração Manual

```python
from whats77 import Whats77

# Inicialização manual (sem .env)
whatsapp = Whats77(
    manager_url="http://localhost:8000",
    manager_api_key="meu_token_super_seguro",
    sender_id="0",
)
```

---

## 🚀 Uso Rápido

### Inicializar

```python
from whats77 import Whats77

whatsapp = Whats77()  # carrega credenciais do .env
```

---

### Enviar Texto

```python
whatsapp.send_text(
    phone_number="+5511999999999",
    message="Olá! Esta mensagem foi enviada pelo Whats77 Manager."
)
```

---

### Enviar Imagem

Aceita **URL**, **data URI** ou **caminho local** (automaticamente convertido para base64):

```python
whatsapp.send_image(
    phone_number="+5511999999999",
    image_path_or_url="/tmp/imagem.jpg",
    caption="Segue imagem de teste",
)
```

> ⚙️ Parâmetros:
>
> * `view_once` (opcional, compatível; ignorado pelo Manager)
> * `is_base64` (opcional; mantido por compatibilidade)

---

### Enviar Documento

Também aceita caminho local ou data URI:

```python
whatsapp.send_document(
    phone_number="+5511999999999",
    file_path="/tmp/relatorio.pdf",
    document_type="pdf",
    caption="Segue o relatório.",
)
```

---

### Enviar Áudio

```python
from whats77 import Whats77

# converter áudio em base64
base64_audio = Whats77.parse_to_base64("/tmp/audio.mp3")

whatsapp.send_audio(
    phone_number="+5511999999999",
    base64_audio=base64_audio,
)
```

---

## 📦 Envio Estruturado em Grupo (novo)

O Whats77 suporta o envio de **múltiplas mensagens em sequência** para o mesmo contato (ou grupo) como um pacote lógico, usando o endpoint `/enqueue_group` do Manager.

Benefícios:

* ✅ **Ordem garantida**: as mensagens são enviadas na sequência definida (`items` na ordem)
* ✅ **Sem intercalação**: outras mensagens não quebram a sequência do grupo
* ✅ **Idempotência por grupo**: o mesmo `group_id` não gera reenvios duplicados
* ✅ **Mesmas regras de segurança**: rate limiting, retentativas e DLQ continuam valendo

### Método: `send_group(...)`

Assinatura:

```python
send_group(
    phone_number_or_group: str,
    items: list[dict],
    group_id: str | None = None,
    priority: str | None = None,
) -> None
```

#### Parâmetros

* `phone_number_or_group`:

  * Número E.164 (`"5511999999999"`, `"+5511999999999"`)
  * Ou ID de grupo WhatsApp (`"120363287726811196-group"`)

* `items`: lista de mensagens a enviar, na ordem desejada.
  Cada item é um `dict` com pelo menos:

  | Campo                              | Tipo   | Obrigatório            | Descrição                                      |
  | ---------------------------------- | ------ | ---------------------- | ---------------------------------------------- |
  | `type`                             | string | ✅                      | `"text"`, `"image"`, `"audio"` ou `"document"` |
  | `text`                             | string | para `type="text"`     | Conteúdo da mensagem                           |
  | `image_path_or_url` ou `image_url` | string | para `type="image"`    | Caminho local, URL ou data URI                 |
  | `audio_base64`                     | string | para `type="audio"`    | Áudio em base64                                |
  | `file_path` ou `document_path`     | string | para `type="document"` | Caminho local, data URI ou identificador       |
  | `document_type`                    | string | opcional               | ex: `"pdf"`, `"xlsx"`                          |

Opcional por item:

* `idempotency_key` → se não informado, o `Whats77` gera automaticamente.
* `image_caption`, `caption`, etc.

#### Exemplo: texto + 2 imagens + PDF

```python
from whats77 import Whats77

whatsapp = Whats77()

whatsapp.send_group(
    phone_number_or_group="5599999999999",
    group_id="relatorio-mensal-2025-01",
    items=[
        {
            "type": "text",
            "text": "Olá! Segue o relatório mensal de Janeiro/2025.",
        },
        {
            "type": "image",
            "image_path_or_url": "/app/graficos/vendas-jan.png",
            "image_caption": "Gráfico de vendas do mês",
        },
        {
            "type": "image",
            "image_path_or_url": "/app/graficos/crescimento-jan.png",
            "image_caption": "Crescimento comparativo",
        },
        {
            "type": "document",
            "file_path": "/app/docs/relatorio-jan-2025.pdf",
            "document_type": "pdf",
            "caption": "Relatório completo em PDF",
        },
    ],
)
```

Se você **não** passar `group_id`, o `Whats77` gera um automaticamente no formato:

```text
group:<destinatario>:<uuid_curto>
```

---

## 🔢 Normalização de Números e IDs de Grupo

```python
from whats77 import Whats77

# Telefone "cru" → E.164 BR
n = Whats77.normalize_phone_number("11999999999")
print(n)  # 5511999999999

print(Whats77.is_valid_whatsapp_number("5511999999999"))
# True
```

### IDs de Grupo do WhatsApp

O adaptador também aceita **IDs de grupo** no formato:

```text
120363287726811196-group
```

Comportamento:

* `normalize_phone_number("120363287726811196-group")` → mantém exatamente o valor.
* `is_valid_whatsapp_number("120363287726811196-group")` → `True`.

Ou seja, você pode usar **o mesmo objeto** `Whats77` para enviar:

* Para números individuais (`send_text`, `send_group`, etc.)
* Para grupos (`send_text`, `send_group`, etc.) passando o ID do grupo.

---

## 🔐 Autenticação

Todas as requisições enviadas ao Manager contêm:

```http
X-API-Key: <sua_chave>
Content-Type: application/json
```

Se a chave for inválida ou ausente:

```json
{"detail": "Invalid or missing API key"}
```

---

## 🧩 Compatibilidade com o Código Antigo

| Função / Comportamento                                    | Mantida?                                 | Observações                                    |
| --------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------- |
| `send_text()`                                             | ✅                                        | Idêntica                                       |
| `send_image()`                                            | ✅                                        | Aceita caminho local / URL / data URI          |
| `send_audio()`                                            | ✅                                        | Idêntica                                       |
| `send_document()`                                         | ✅                                        | Aceita caminho local / data URI                |
| `send_group()`                                            | 🆕                                       | Novo método para envio sequencial em grupo     |
| `parse_to_base64()`                                       | ✅                                        | Utilitária igual                               |
| `normalize_phone_number()` / `is_valid_whatsapp_number()` | ✅                                        | Agora também aceitam IDs de grupo (`*-group`)  |
| Parâmetro `is_base64`                                     | ✅                                        | Mantido por compatibilidade                    |
| Campos `instance_id`, `token`                             | ⚙️ Opcional / legado                     | Usados só se `MANAGER_URL` não for configurada |
| Base URL API direta Z-API                                 | ❌ Não usada — o Manager cuida dos envios |                                                |

> Você pode substituir seu módulo antigo pelo novo `whats77.py` sem alterar chamadas existentes.
> O uso de `send_group()` é **opcional**, mas recomendado para fluxos com múltiplas mensagens sequenciais.

---

## 🧠 Como Funciona Internamente

1. `Whats77.send_*()` monta um `payload JSON` contendo:

   * `idempotency_key` (gerada automaticamente)
   * `sender_id`, `to`, `text`/`image_url`/`document_path`/`audio_base64`
   * `priority` (`default` por padrão)

2. Para mensagens individuais, o payload é enviado para:

   ```http
   POST {MANAGER_URL}/enqueue
   ```

3. Para grupos (`send_group()`), o payload é enviado para:

   ```http
   POST {MANAGER_URL}/enqueue_group
   ```

4. O Manager:

   * Valida `X-API-Key`
   * Enfileira no Redis (`outbox`, `retry`, `dlq`, etc.)
   * O worker processa e dispara via Z-API respeitando:

     * Rate limiting
     * Retentativas com backoff exponencial
     * Horário de silêncio (*quiet hours*)

---

## 🧾 Resposta de Exemplo do Manager

**Mensagens individuais (`/enqueue`):**

```json
{
  "queued": true,
  "queue": "queue:outbox:default",
  "idempotent": false
}
```

**Grupos (`/enqueue_group`):**

```json
{
  "queued": true,
  "queue": "queue:outbox:default",
  "group_id": "relatorio-mensal-2025-01",
  "items_queued": 4,
  "items_skipped": 0,
  "total_items": 4
}
```

---

## 🧰 Dependências

| Biblioteca      | Versão mínima |
| --------------- | ------------- |
| `requests`      | 2.0.0         |
| `python-dotenv` | 0.21.0        |

Instale com:

```bash
pip install requests python-dotenv
```

---

## 🏁 Migração Rápida

1. Substitua seu arquivo antigo `whats77.py` pelo novo adaptador.

2. Adicione no `.env` as variáveis do Manager:

   ```env
   MANAGER_URL=http://localhost:8000
   MANAGER_API_KEY=meu_token_super_seguro
   SENDER_ID=0
   ```

3. Rode seu código existente — nenhuma alteração nas chamadas antigas é necessária.

4. Para fluxos com várias mensagens sequenciais (relatórios, campanhas, etc.),
   comece a usar `send_group()` para ter **ordem garantida** e **idempotência por grupo**.