Portfólio GD GitHub Vamos conversarConversar
← Todos os projetos

Automação

Robô de WhatsApp com IA

Um assistente que responde no meu WhatsApp, marca compromissos no Google Agenda e mostra tudo em um aplicativo próprio.

PythonDockerWAHAClaudeSQLiteGoogle Calendar API
Em uso · aplicativo localCriado e implementado por mim

Telas reais

O aplicativo, tela por tela.

Aplicativo real, rodando em uma cópia isolada com conversas, números e compromissos fictícios.

robo-do-whatsapp · conversasAmpliar
Aplicativo do robô com a lista de conversas e uma conversa aberta: pedido de agendamento, prévia com código e confirmação

Tela 1 de 5

Cada conversa, como numa tela de WhatsApp.

O aplicativo mostra o que o contato escreveu, o que o robô respondeu e o que eu digitei à mão. A lista se atualiza sozinha a cada 4 segundos.

  • Situação do robô e do WhatsApp no topo
  • Etiquetas mostram quem recebe resposta
  • Somente leitura: o painel não envia mensagens
robo-do-whatsapp · pausa manualAmpliar
Conversa com uma cliente fictícia em que o robô responde, passa um assunto ao dono e fica em silêncio depois da resposta manual

Tela 2 de 5

Quando eu entro na conversa, o robô sai.

Uma resposta minha à mão pausa aquela conversa por uma hora e cancela a resposta que o robô estava preparando.

  • Assunto delicado: o robô avisa e passa para mim
  • Pausa visível na lista de conversas
  • As outras conversas seguem normalmente
robo-do-whatsapp · agendaAmpliar
Aba Agenda com quatro compromissos fictícios, um deles marcado como criado pelo robô

Tela 3 de 5

Os compromissos, direto do Google Agenda.

A aba consulta a API oficial do Google e marca quais compromissos foram criados pelo robô.

  • Próximos 30 dias, no horário de Brasília
  • Cache de um minuto para não repetir consultas
  • Alterar ou cancelar só com prévia e confirmação no WhatsApp
robo-do-whatsapp · lembretesAmpliar
Aba Lembretes com dois lembretes pendentes e botão de cancelar

Tela 4 de 5

O que o robô vai lembrar.

Quando alguém pede para ser lembrado, o robô guarda e envia a mensagem na hora, uma única vez.

  • Lembrete de compromisso criado junto com o evento
  • Cancelar pelo painel ou pedindo na conversa
  • Lembrete de agenda só sai junto com o compromisso
robo-do-whatsapp · números liberadosAmpliar
Aba Números liberados com o número do dono, dois números fictícios liberados e o campo para liberar outro

Tela 5 de 5

Quem o robô pode responder.

No modo dono, o robô responde só ao meu número e aos números desta lista. A mudança vale em segundos, sem reiniciar.

  • Três modos: simulado, dono e todos
  • Grupos, canais e status ficam de fora
  • Limite contra loop: 10 respostas em 10 minutos por conversa

O desafio

Onde tudo começou.

Responder mensagens e anotar compromissos toma o dia. Um robô que responde sozinho precisa de freios: não pode falar por cima de mim, repetir mensagem, entrar em loop nem mexer na agenda por conta própria.

A solução

Da ideia à implementação.

Montei o caminho inteiro: o WhatsApp ligado por um container Docker, um robô em Python que lê as conversas e pede a resposta ao Claude, memória em SQLite, integração com a API oficial do Google Agenda e um aplicativo de janela própria para acompanhar tudo em tempo real.

Como funciona

Por dentro do fluxo.

O WAHA roda em Docker e expõe o WhatsApp como uma API HTTP, só neste computador. A cada segundo o robô consulta as conversas, grava as mensagens novas no SQLite e, um segundo depois da última mensagem do contato, pede a resposta ao Claude e envia. Pedido de agenda vira uma intenção estruturada: o Python valida, mostra uma prévia e só grava no Google depois do código de confirmação.

  1. 1WhatsApp
  2. 2WAHA em Docker
  3. 3Robô em Python
  4. 4Claude
  5. 5SQLite
  6. 6Google Agenda
  7. 7Aplicativo

WhatsApp

  • Contato escrevetexto ou áudio
  • Eu, à mãoo robô se cala por 1 hora

WAHA em Docker

  • API HTTP localsó em 127.0.0.1
  • Sessão em volumereinicia sem novo QR code
  • Imagem fixadapor digest

Robô em Python

  • Coletalê as conversas a cada segundo
  • Regraspausas, limites e modos
  • Claudesó texto, sem ferramentas
  • Áudiotranscrito no próprio PC

Memória e saídas

  • SQLitehistórico, lembretes e vínculos
  • Google AgendaAPI oficial, com confirmação
  • Aplicativoconversas e agenda em tempo real
O Claude nunca fala direto com o WhatsApp nem com a agenda: tudo passa pelas regras do robô.

Linha do tempo

Como o robô foi construído e atualizado.

  1. 8/10/2026Docker e WhatsApp no ar

    Docker Desktop com WSL 2, WAHA em container e o número pareado por QR code.

  2. 8 e 9/10Robô de texto e áudio

    Memória por conversa, pausa manual, limites contra loop e áudio transcrito no próprio PC.

  3. 9/10Lembretes e aplicativo

    Lembretes e anotações, respostas mais rápidas e o painel em janela própria.

  4. 9/10Google Agenda

    API oficial com OAuth, prévia, código de confirmação e a aba Agenda no aplicativo.

Por dentro do código

Do container ao aplicativo, passo a passo.

Trechos do projeto, simplificados. Chaves e números ficam fora do código, em arquivos locais.

1O WhatsApp vira uma API, dentro do Docker.compose.yaml

O WAHA roda em um container e só atende este computador. A sessão do WhatsApp fica em um volume, então reiniciar o container não pede novo QR code. A imagem é fixada por digest para não mudar sozinha.

compose.yaml
services:
  waha:
    image: ${WAHA_IMAGE:-devlikeapro/waha:latest}   # fixada por digest no .env
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"        # só este computador enxerga a API
    env_file:
      - .env                         # chave da API, fora do Git
    volumes:
      - waha_sessions:/app/.sessions # a sessão sobrevive ao reinício
      - waha_media:/tmp/whatsapp-files

volumes:
  waha_sessions:
  waha_media:
2Um cliente pequeno para ler e enviar.robo/waha.py

O robô fala com o WAHA por poucas chamadas: listar conversas, ler mensagens e enviar texto. A chave vai no cabeçalho e nunca acompanha um redirecionamento; o robô não cria nem encerra a sessão.

robo/waha.py
class Waha:
    def conversas_recentes(self, limite):
        return self._pedir('GET', '/api/%s/chats/overview?limit=%d'
                           % (self.sessao, limite)) or []

    def mensagens(self, chat, limite):
        return self._pedir('GET', '/api/%s/chats/%s/messages?limit=%d'
                           % (self.sessao, quote(chat, safe=''), limite)) or []

    def enviar_texto(self, chat, texto):
        resposta = self._pedir('POST', '/api/sendText', {
            'session': self.sessao, 'chatId': chat, 'text': texto})
        return id_de(resposta)
3O laço do robô.robo/nucleo.py

A cada ciclo o robô coleta as mensagens novas, responde as conversas cuja espera venceu e envia os lembretes da hora. Se uma resposta falha, ele se cala em vez de repetir.

robo/nucleo.py
def ciclo(self):
    self.coletar()                    # lê o WAHA e grava o que é novo
    for conversa in self.estado.vencidas(self.relogio()):
        try:
            self.responder(conversa)  # histórico da conversa -> Claude -> envio
        except Exception:
            # falhou: a pendência é baixada, nunca fica para depois
            self.estado.cancelar(conversa['chat'])
    self.enviar_lembretes()           # o que venceu sai uma única vez
4Onde a informação fica guardada.robo/estado.py

Tudo fica em um arquivo SQLite no próprio computador: histórico por conversa, pausas, lembretes, anotações e os vínculos com a agenda. O registro de avisos não guarda o conteúdo das mensagens.

robo/estado.py
CREATE TABLE IF NOT EXISTS historico (
    n INTEGER PRIMARY KEY AUTOINCREMENT, chat TEXT NOT NULL,
    papel TEXT NOT NULL, texto TEXT NOT NULL, ts INTEGER NOT NULL);
CREATE TABLE IF NOT EXISTS conversas (
    chat TEXT PRIMARY KEY, nome TEXT, numero TEXT,
    pausa_ate REAL NOT NULL DEFAULT 0, responder_em REAL);
CREATE TABLE IF NOT EXISTS lembretes (
    n INTEGER PRIMARY KEY AUTOINCREMENT, chat TEXT NOT NULL,
    quando REAL NOT NULL, texto TEXT NOT NULL,
    estado TEXT NOT NULL DEFAULT 'pendente');
CREATE TABLE IF NOT EXISTS agenda_eventos (
    n INTEGER PRIMARY KEY AUTOINCREMENT, chat TEXT NOT NULL,
    evento_id TEXT NOT NULL UNIQUE, titulo TEXT NOT NULL,
    inicio TEXT NOT NULL, estado TEXT NOT NULL, lembrete_n INTEGER);
5A IA propõe, o código decide.robo/nucleo.py

O Claude não recebe ferramenta nenhuma: devolve texto e marcações, como um pedido de lembrete. Quem valida a data, aplica os limites e executa é o Python. Se o assunto depende de mim, a marcação avisa o contato e me chama.

robo/nucleo.py
SILENCIO = '[SILENCIO]'   # o Claude escreve isto para não mandar nada
PASSAR = '[PASSAR]'       # a conversa precisa do próprio Guilherme
MARCA_LEMBRAR = re.compile(
    r'\[\s*LEMBRAR\s+(\d{4}-\d{2}-\d{2})\s+(\d{1,2}:\d{2})\s*\|\s*([^\]]+)\]')

PAUSA_MANUAL = 3600       # respondi à mão: 1 hora de silêncio naquela conversa
MAX_POR_CONVERSA = 10     # respostas em 10 minutos; acima disso é provável loop
LIMITE_HISTORICO = 40     # mensagens da conversa enviadas ao Claude
6Agenda oficial do Google, com confirmação.robo/agenda.py

A integração usa OAuth e o escopo mínimo da Calendar API. O pedido vira uma prévia com código que vale 15 minutos; só a confirmação grava. O identificador do evento nasce antes, então repetir a confirmação depois de uma queda recupera o mesmo evento em vez de duplicar.

robo/agenda.py
codigo = uuid.uuid4().hex[:8]
evento_id = 'robo' + uuid.uuid4().hex   # criado antes de falar com o Google
db.execute('INSERT INTO agenda_propostas VALUES (?,?,?,?,?,?,?,?)',
           (codigo, chat, json.dumps(dados), evento_id,
            agora + 900, 'pendente', calendario, None))
return ('Para confirmar, escreva: confirmar agenda %s '
        '(vale por 15 minutos).' % codigo)
7Um aplicativo para acompanhar tudo.robo/painel.py

O painel é um servidor local que só responde a este computador e exige um token para qualquer alteração. Um atalho abre tudo em janela própria, com o robô rodando por baixo.

robo/painel.py
def atender(self, acao):
    if self.headers.get('Host') not in HOSTS:   # só 127.0.0.1
        return self.responder(403, {'erro': 'Acesso so por este PC.'})
    cfg = carregar()
    estado = Estado(cfg.dados / 'robo.sqlite')
    self.responder(200, acao(cfg, estado))      # lê o banco do robô

Em resumo

  • WhatsApp em container Docker, acessível só neste computador
  • Respostas do Claude com histórico separado por conversa
  • Se eu respondo à mão, o robô se cala por 1 hora
  • Agenda oficial do Google com prévia e código de confirmação
  • Lembretes, anotações e áudio transcrito no próprio computador
  • 144 testes automatizados

Minha parte

Criei e implementei tudo: a instalação em Docker, o cliente da API, o robô e suas regras, a memória em SQLite, a integração com o Google Agenda, o painel e o aplicativo.

Resultado e situação

Robô em uso no meu computador, respondendo ao meu número e aos números liberados. A suíte tem 144 testes, com WhatsApp e Claude simulados. A consulta real ao Google Agenda foi verificada em 9/10/2026; criação e cancelamento passam por prévia e código de confirmação e são cobertos por testes com o Google simulado.

Contexto do projeto

Projeto autoral, de uso pessoal. As telas mostram o aplicativo real rodando em uma cópia isolada, com conversas, números e compromissos fictícios: nenhuma conversa verdadeira aparece. A conexão com o WhatsApp é feita pelo WAHA, em container; a agenda usa a API oficial do Google.