Metadata-Version: 2.5
Name: shade-plugins-slack
Version: 0.1.0
Summary: Listar canais e enviar mensagem no Slack sem duplicar, pela Web API, com o token OAuth da conexão da plataforma.
Author: ShadeOne
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: shade-sdk<0.23,>=0.22.3
Description-Content-Type: text/markdown

# shade-plugins-slack

Lista os canais e envia mensagem no [Slack](https://api.slack.com/web), de dentro de um robô do
[`shade-sdk`](https://pypi.org/project/shade-sdk/), com o token OAuth da conexão do Slack na
plataforma. 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 a conexão em `[conexoes]`:

```toml
[hosts]
permitidos = ["slack.com"]

[conexoes]
nomes = ["slack"]

[plugins]
slack = "0.1.0"
```

```python
import shade_plugins_slack as slack

# Leitura: roda inclusive no ensaio.
canal = next(c["id"] for c in slack.listar_canais() if c["nome"] == "financeiro")

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

## A conta

- **Conexão:** o robô nunca vê a senha nem o refresh token. O token de acesso chega sob demanda da
  conexão `slack` da plataforma e é renovado por ela. Fora da plataforma (no laptop), ponha um
  token de acesso em `SHADE_TOKEN_SLACK`.
- **Escopos:** `channels:read` (listar os canais), `chat:write` (postar) e `channels:history` (ler o
  histórico recente, que é o que evita a mensagem duplicada). A conexão sem `channels:history` não
  posta nada e o erro manda reconectar o Slack.
- **Canais:** os públicos. O app precisa ter sido adicionado ao canal para ler o histórico e postar.
  O `canal` das operações é o **id** (como `C0123ABCD`, o que `listar_canais` devolve), não o nome.

## O que o plugin garante

- **Não duplica a mensagem.** O Slack não reconhece repetição. O plugin grava a chave do item no
  `metadata` da mensagem e, antes de postar, lê as últimas mensagens do canal (até 500) e devolve a
  que já tiver a chave. Depois que o envio saiu sem confirmação (a conexão caiu, o `5xx`, a
  resposta que não se lê), o plugin não posta de novo: ele confere o histórico e, sem achar, o
  erro é `SlackIncerto` (o item fica retido para uma pessoa conferir no canal e, só se a mensagem
  não chegou, mandar refazer).
- **O texto é literal.** `&`, `<` e `>` vão como as entidades que a API documenta, então
  `<!channel>`, `<@U123>` e `<https://...|texto>` vindos de um dado não viram menção nem link. A
  mensagem sai sem formatação (`mrkdwn` desligado), e o corpo é montado como objeto e serializado
  pelo `shade_sdk.http`, nunca por concatenação de texto.
- **Retentativa só onde é segura:** a leitura e o `429` (nada foi postado) repetem dentro do
  `timeout=`; depois do despacho do `chat.postMessage`, nada repete.
- **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ó `slack.com`, com o certificado verificado, sem seguir redirecionamento; toda
  chamada aceita `timeout=` (30 s por padrão), que vale para a chamada inteira.
- **Sem conteúdo 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 id do canal, o token nem o corpo da resposta; o erro da API
  chega só pelo código que o Slack dá (`channel_not_found`, `missing_scope`).

## Operações

| Operação | Efeito | O que faz |
|---|---|---|
| `listar_canais(*, limite=1000, arquivados=False)` | não | Os canais públicos: `id`, `nome`, `arquivado`, `membro`, `membros`. |
| `enviar_mensagem(canal, texto)` | sim | Posta `texto` (até 40 000 caracteres) no canal. Devolve `canal`, `ts` e `reaproveitada`. |

## Limites

O histórico do Slack é de leitura limitada para alguns apps (os não listados no Marketplace têm
cota baixa em `conversations.history`). Se a leitura for recusada com `ratelimited`, a mensagem não
é postada (`SlackNaoSaiu`), em vez de arriscar o duplicado.

## Testes

A suíte de contrato do `shade_sdk.testing.plugins` e um servidor falso da Web API (TLS de uma CA de
teste, que responde `ok: false` com `200` e não reconhece repetição, como a de verdade) acompanham o
código-fonte em `tests/`.
