Metadata-Version: 2.5
Name: shade-plugins-telegram
Version: 0.1.0
Summary: Enviar mensagem e documento pela Bot API do Telegram, sem nunca repetir um envio depois de despachado.
Author: ShadeOne
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: shade-sdk<0.23,>=0.22.1
Description-Content-Type: text/markdown

# shade-plugins-telegram

Envia mensagem e documento pela [Bot API do Telegram](https://core.telegram.org/bots/api), de
dentro de um robô do [`shade-sdk`](https://pypi.org/project/shade-sdk/). Só a biblioteca padrão do
Python: nenhuma dependência além do `shade-sdk`.

No `shade.toml` do robô, o host da API vai em `[hosts] permitidos` e o token do bot em
`[credenciais]`:

```toml
[hosts]
permitidos = ["api.telegram.org"]

[credenciais]
nomes = ["TELEGRAM_BOT_TOKEN"]

[plugins]
telegram = "0.1.0"
```

```python
import shade_plugins_telegram as telegram

# Escrita: só dentro de item.efetivar() (no ensaio não roda).
with item.efetivar("avisar o time"):
    telegram.enviar_mensagem(config["telegram_chat"], f"Pedido {item['pedido']} conferido.")

with item.efetivar("mandar o comprovante"):
    telegram.enviar_documento(
        config["telegram_chat"],  # o número do chat, ou @canal
        "saida/comprovante.pdf",  # um arquivo do diretório de trabalho do robô
        legenda="Segue o comprovante.",
    )
```

## A conta

- **Token:** o token que o [@BotFather](https://t.me/BotFather) entrega ao criar o bot. Guarde-o
  como a credencial `TELEGRAM_BOT_TOKEN`; o plugin o lê só por `segredo()`.
- **`chat_id`:** o número do chat (positivo para conversa com uma pessoa, `-100...` para grupo e
  canal) ou `@canal_publico`. Não é segredo, mas identifica uma pessoa: guarde-o no `[config]` do
  robô e não o ponha em log. O bot só escreve a quem já falou com ele (`/start`) e nos grupos e
  canais em que foi adicionado.
- **Texto puro:** a mensagem e a legenda vão sem `parse_mode`, então `*`, `_` e `<` aparecem como
  são. Formatação (Markdown, HTML) não é suportada nesta versão, nem botão, foto, áudio ou
  resposta a outra mensagem.
- **Documento:** até 50 MB, do diretório de trabalho do robô (caminho relativo). `nome` troca o
  nome que aparece no chat (sem aspas, barras nem controle: o que não serve é recusado, não
  escapado), `legenda` leva até 1024 caracteres.

## O que o plugin garante

- **Nunca envia duas vezes.** A Bot API não reconhece repetição: não há chave de idempotência, e
  um segundo pedido é uma segunda mensagem no chat. Por isso o plugin **não repete depois do
  despacho, em nenhum erro**:
  - A conexão que não chegou a abrir (recusada, nome que não resolve, rede inalcançável) é tentada
    de novo, dentro do `timeout=`, porque nada saiu.
  - A API que recusou (4xx) é `TelegramNaoSaiu`: nenhuma mensagem saiu. O `codigo` vem em
    `erro.codigo` e, no limite de envio (429), o `retry_apos` em segundos. O plugin não espera
    nem repete sozinho: a espera é decisão do robô.
  - O que sobra é `TelegramIncerto`: o `timeout`, a conexão que cai depois do envio, o `5xx` e a
    resposta de sucesso sem o `message_id`. A mensagem **pode** estar no chat. O item fica
    retido para uma pessoa conferir no chat e, só se a mensagem não chegou, mandar refazer. O
    plugin não reenvia sozinho, e uma nova execução do robô também não.
- **O token não aparece em lugar nenhum.** A Bot API o põe no **caminho da URL**
  (`/bot<token>/sendMessage`), então a URL é um segredo: ela só existe dentro do envio. Mensagem
  de erro, evento e relatório mostram o endpoint como `/bot<token>/sendMessage`, e o erro do
  `urllib` (que guarda a URL inteira) nunca é a causa de um erro do plugin.
- **Só dentro de `item.efetivar()`.** Fora dele, a operação é um erro de contrato antes de qualquer
  byte; no ensaio, o bloco é pulado e nada é enviado.
- **Host e prazo:** só `api.telegram.org`, com o certificado verificado, sem seguir
  redirecionamento; toda chamada aceita `timeout=` (30 s por padrão), que vale para a chamada
  inteira.
- **Sem dado pessoal em evento nem em erro:** cada chamada emite um evento com a operação, o host,
  a duração e o status. Nunca o texto, o `chat_id`, o nome do arquivo nem o corpo da resposta; o
  erro da API chega só pelo código numérico e por uma explicação escrita no plugin.

## Operações

| Operação | Efeito | O que faz |
|---|---|---|
| `enviar_mensagem(chat_id, texto, *, previa_de_link=False)` | sim | Mensagem de texto de até 4096 caracteres. |
| `enviar_documento(chat_id, documento, *, nome=None, legenda=None, tipo=None)` | sim | Arquivo do diretório de trabalho como documento. |

As duas devolvem `{"mensagem_id": 1234}`.

## Testes

A suíte de contrato do `shade_sdk.testing.plugins` e um servidor falso da Bot API (TLS de uma CA de
teste, com o token no caminho como na de verdade) acompanham o código-fonte em `tests/`. O servidor
falso não reconhece repetição, como a API de verdade.
