Saltar para o conteúdo principal
Este guia ajudará você a criar um bot simples do WhatsApp que responde a mensagens. Você aprenderá os conceitos principais e terá um bot funcionando ao final.

Exemplo básico

Aqui está um bot mínimo que responde a mensagens “ping”:
src/main.rs
use std::sync::Arc;
use whatsapp_rust::bot::Bot;
use whatsapp_rust::TokioRuntime;
use whatsapp_rust::store::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;
use wacore::types::events::Event;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Inicializa o backend de armazenamento
    let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);

    // Constrói o bot
    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .with_runtime(TokioRuntime)
        .on_event(|event, client| async move {
            match &*event {
                Event::PairingQrCode { code, .. } => {
                    println!("Scan this QR code with WhatsApp:\n{}", code);
                }
                Event::Message(msg, info) => {
                    println!("Message from {}: {:?}", info.source.sender, msg);
                }
                _ => {}
            }
        })
        .build()
        .await?;

    // Inicia o bot
    bot.run().await?.await?;
    Ok(())
}

Passo a passo detalhado

1

Configure o backend de armazenamento

O bot precisa de armazenamento persistente para dados de sessão, chaves e estado:
let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);
Isso cria um arquivo de banco SQLite chamado whatsapp.db no diretório atual. A sessão será persistida entre reinicializações.
2

Configure o builder do bot

O padrão Bot::builder() permite que você configure todos os componentes obrigatórios:
let mut bot = Bot::builder()
    .with_backend(backend)
    .with_transport_factory(TokioWebSocketTransportFactory::new())
    .with_http_client(UreqHttpClient::new())
    .with_runtime(TokioRuntime)
Todos os quatro componentes (backend, transporte, cliente HTTP, runtime) são obrigatórios. O builder usa um padrão typestate — seu código não compilará se algum estiver faltando.
3

Trate eventos

Use .on_event() para tratar eventos que chegam do WhatsApp:
.on_event(|event, client| async move {
    match &*event {
        Event::PairingQrCode { code, .. } => {
            println!("QR Code:\n{}", code);
        }
        Event::Message(msg, info) => {
            // Trate a mensagem recebida
        }
        Event::Connected(_) => {
            println!("Connected successfully!");
        }
        _ => {}
    }
})
O manipulador de eventos recebe dois parâmetros:
  • event: Um Arc<Event> — use &*event ou event.as_ref() para fazer pattern-matching no tipo interno do evento
  • client: Um Arc<Client> que você pode usar para enviar mensagens ou chamar métodos da API
4

Construa e execute o bot

Construa o bot e inicie o loop de eventos:
.build()
.await?;

bot.run().await?.await?;
O duplo .await? é intencional:
  • O primeiro .await? inicia o bot e retorna um BotHandle
  • O segundo .await? aguarda o bot terminar a execução

Respondendo a mensagens

Vamos estender o bot para responder “pong” a mensagens “ping”:
use wacore::proto_helpers::MessageExt;
use waproto::whatsapp as wa;

.on_event(|event, client| async move {
    match &*event {
        Event::PairingQrCode { code, .. } => {
            println!("QR Code:\n{}", code);
        }
        Event::Message(msg, info) => {
            // Verifica se a mensagem é um texto dizendo "ping"
            if let Some(text) = msg.text_content() {
                if text == "ping" {
                    // Cria a mensagem de resposta
                    let reply = wa::Message {
                        conversation: Some("pong".to_string()),
                        ..Default::default()
                    };

                    // Envia a resposta
                    if let Err(e) = client.send_message(info.source.chat.clone(), reply).await {
                        eprintln!("Failed to send reply: {}", e);
                    }
                }
            }
        }
        _ => {}
    }
})

Métodos principais

  • msg.text_content() - Extrai o texto de qualquer tipo de mensagem (conversation, extended text, etc.)
  • client.send_message() - Envia uma mensagem para um chat
  • info.source.chat - O JID (identificador) do chat de onde a mensagem veio
  • info.source.sender - O JID do usuário que enviou a mensagem

Métodos de autenticação

Pareamento por QR code (padrão)

O bot gera automaticamente códigos QR quando não está autenticado. Escaneie com seu celular para vincular:
Event::PairingQrCode { code, .. } => {
    println!("Scan this QR code:\n{}", code);
}

Pair code (número de telefone)

Alternativamente, vincule usando um número de telefone e um código de 8 dígitos:
use whatsapp_rust::pair_code::PairCodeOptions;

let mut bot = Bot::builder()
    .with_backend(backend)
    .with_transport_factory(TokioWebSocketTransportFactory::new())
    .with_http_client(UreqHttpClient::new())
    .with_runtime(TokioRuntime)
    .with_pair_code(PairCodeOptions {
        phone_number: "15551234567".to_string(),
        ..Default::default()
    })
    .on_event(|event, client| async move {
        match &*event {
            Event::PairingCode { code, .. } => {
                println!("Enter this code on your phone: {}", code);
            }
            _ => {}
        }
    })
    .build()
    .await?;
PairCodeOptions deriva companion_platform_id e companion_platform_display do PlatformType do dispositivo por padrão (Chrome com Chrome (Linux) para o perfil web padrão). Você pode sobrescrever o id no wire quando necessário:
use whatsapp_rust::pair_code::PairCodeOptions;
use wacore::companion_reg::CompanionWebClientType;

PairCodeOptions {
    phone_number: "15551234567".to_string(),
    show_push_notification: true,
    custom_code: Some("ABCD1234".to_string()), // ou None para aleatório
    // `None` deriva automaticamente de `Device.device_props.platform_type`.
    platform_id: Some(CompanionWebClientType::Chrome),
}
platform_id aceita o enum no wire CompanionWebClientType (ids ASCII de um único byte). A string de exibição é sempre derivada — não há um campo separado platform_display.
A autenticação por pair code e por QR code rodam simultaneamente. O método que for concluído primeiro será o usado.

Executando o bot

1

Primeira execução - Autenticação

Na primeira execução, o bot irá gerar um QR code:
cargo run
Escaneie o QR code com o WhatsApp no seu celular:
  1. Abra o WhatsApp no seu celular
  2. Vá em Configurações → Aparelhos conectados
  3. Toque em “Conectar um aparelho”
  4. Escaneie o QR code exibido no seu terminal
2

Execuções seguintes - Login automático

Após o pareamento, a sessão é salva. O bot irá reconectar automaticamente:
cargo run
Você deverá ver:
Connected successfully!
3

Teste o bot

Envie “ping” para o seu bot de qualquer chat do WhatsApp. Ele deve responder com “pong”!

Flags de CLI do binário de demonstração

O repositório inclui um binário de bot de demonstração (src/main.rs) que suporta argumentos de CLI para autenticação:
cargo run                                      # Pareamento somente por QR code
cargo run -- --phone 15551234567               # Pair code + QR code (concorrentes)
cargo run -- -p 15551234567                    # Forma curta
cargo run -- -p 15551234567 --code MYCODE12    # Pair code personalizado de 8 caracteres
cargo run -- -p 15551234567 -c MYCODE12        # Forma curta
O bot de demonstração responde a 🦀ping com uma resposta citada 🏓 Pong!, edita a resposta para anexar a latência de envio e suporta ping/pong de mídia via reuso de CDN.

Usando MessageContext

Para um tratamento de mensagens mais limpo, use MessageContext para encapsular a mensagem, os metadados e o cliente juntos. Isso fornece métodos convenientes como send_message (que mira automaticamente o chat de origem), build_quote_context, edit_message e revoke_message:
use whatsapp_rust::bot::{Bot, MessageContext};

.on_event(|event, client| async move {
    if let Some(ctx) = MessageContext::from_event(&event, client) {
        handle_message(&ctx).await;
    }
})
Em seguida, defina funções de tratamento focadas:
async fn handle_message(ctx: &MessageContext) {
    if let Some(text) = ctx.message.text_content() {
        if text == "ping" {
            let reply = wa::Message {
                conversation: Some("pong".to_string()),
                ..Default::default()
            };
            if let Err(e) = ctx.send_message(reply).await {
                eprintln!("Failed to send: {}", e);
            }
        }
    }
}

Encaminhamento de mídia com reuso de CDN

Você também pode encaminhar mídia instantaneamente reusando os campos originais do CDN — sem necessidade de download ou re-upload:
/// Reutiliza o blob original do CDN, apenas troca a legenda.
/// Instantâneo independentemente do tamanho do arquivo.
fn build_media_reply(message: &wa::Message) -> Option<wa::Message> {
    let base = message.get_base_message();
    if let Some(img) = &base.image_message {
        return Some(wa::Message {
            image_message: Some(Box::new(wa::message::ImageMessage {
                caption: Some("Received your image!".to_string()),
                ..*img.clone()
            })),
            ..Default::default()
        });
    }
    None
}
Veja o guia de encaminhamento de mídia para mais detalhes.

Exemplo completo com logging

Aqui está um exemplo pronto para produção com logging adequado, reações, edição de mensagens e reuso de CDN para mídia:
src/main.rs
use chrono::{Local, Utc};
use log::{error, info};
use std::sync::Arc;
use wacore::proto_helpers::MessageExt;
use wacore::types::events::Event;
use waproto::whatsapp as wa;
use whatsapp_rust::bot::{Bot, MessageContext};
use whatsapp_rust::TokioRuntime;
use whatsapp_rust::store::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;

const PING_TRIGGER: &str = "🦀ping";
const PONG_TEXT: &str = "🏓 Pong!";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
        .format(|buf, record| {
            use std::io::Write;
            writeln!(
                buf,
                "{} [{:<5}] [{}] - {}",
                Local::now().format("%H:%M:%S"),
                record.level(),
                record.target(),
                record.args()
            )
        })
        .init();

    let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);
    info!("SQLite backend initialized");

    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .with_runtime(TokioRuntime)
        .on_event(|event, client| async move {
            match &*event {
                Event::PairingQrCode { code, .. } => {
                    println!("\n{}", code);
                }
                Event::Message(msg, info) => {
                    let ctx = MessageContext::from_parts(msg, info, client);
                    handle_message(&ctx).await;
                }
                Event::Connected(_) => info!("Bot connected!"),
                Event::LoggedOut(_) => error!("Bot was logged out!"),
                _ => {}
            }
        })
        .build()
        .await?;

    info!("Starting bot...");
    bot.run().await?.await?;
    Ok(())
}

async fn handle_message(ctx: &MessageContext) {
    // Tenta a resposta de mídia via reuso de CDN primeiro (instantânea, sem download)
    if let Some(reply) = build_media_pong(&ctx.message) {
        if let Err(e) = ctx.send_message(reply).await {
            error!("Failed to send media pong: {}", e);
        }
        return;
    }

    // Trata o ping de texto
    if ctx.message.text_content() == Some(PING_TRIGGER) {
        let context_info = ctx.build_quote_context();
        let reply = wa::Message {
            extended_text_message: Some(Box::new(wa::message::ExtendedTextMessage {
                text: Some(PONG_TEXT.to_string()),
                context_info: Some(Box::new(context_info)),
                ..Default::default()
            })),
            ..Default::default()
        };

        if let Err(e) = ctx.send_message(reply).await {
            error!("Failed to send pong: {}", e);
        }
    }
}

/// Reutiliza o blob original do CDN, apenas troca a legenda.
/// Instantâneo independentemente do tamanho do arquivo — sem download ou re-upload.
fn build_media_pong(message: &wa::Message) -> Option<wa::Message> {
    let base = message.get_base_message();
    if let Some(img) = &base.image_message
        && img.caption.as_deref() == Some(PING_TRIGGER)
    {
        return Some(wa::Message {
            image_message: Some(Box::new(wa::message::ImageMessage {
                caption: Some(PONG_TEXT.to_string()),
                ..*img.clone()
            })),
            ..Default::default()
        });
    }
    if let Some(vid) = &base.video_message
        && vid.caption.as_deref() == Some(PING_TRIGGER)
    {
        return Some(wa::Message {
            video_message: Some(Box::new(wa::message::VideoMessage {
                caption: Some(PONG_TEXT.to_string()),
                ..*vid.clone()
            })),
            ..Default::default()
        });
    }
    None
}

Configurando alvos de log

whatsapp-rust usa o crate log com alvos específicos por módulo para filtragem detalhada. Você pode usar RUST_LOG para controlar quais componentes emitem saída de log.

Alvos de log disponíveis

AlvoDescrição
Client/KeepalivePings/pongs de keepalive e detecção de socket morto
Client/RecvProcessamento de frames recebidos (unmarshal, descompressão)
Client/SendCriptografia e despacho de mensagens enviadas
Client/OfflineSyncProgresso e tempo da sincronização de mensagens offline
Client/AppStateSincronização do estado do app (contatos, configurações, etc.)
Client/AccountSyncOperações de sincronização em nível de conta
Client/PairCodeFluxo de autenticação por pair code
Client/PairFluxo de pareamento por QR code
Client/ReceiptProcessamento de recibos (lido, entregue, reproduzido)
Client/TcTokenOperações de token de contato confiável
Client/GroupOperações de metadados e participantes de grupos
Client/ContactsSincronização e consulta de contatos
Client/BusinessAtualizações de perfil business
Client/PDOPeer Data Operations — recuperação de mensagens do telefone principal via mensagens peer com JID nu e fallback de retry
Client/IQEnvio/recebimento de stanzas IQ
Client/AckConfirmação de stanzas
Client/StatusOperações de status/stories
Client/PictureAtualizações de foto de perfil
Client/UnifiedSessionEstabelecimento de sessão
BlockingOperações de bloqueio/desbloqueio
ChatstateEventos de indicador de digitação
PresenceHandlerProcessamento de atualizações de presença
AppStateCodificação/decodificação de patches do estado do app
Bot/PairCodeTratamento de pair code em nível de bot

Exemplos de filtragem

# Mostra apenas logs de conexão e mensagens
RUST_LOG="Client/Keepalive=debug,Client/Send=debug,Client/Recv=trace" cargo run

# Depura problemas de sync offline
RUST_LOG="Client/OfflineSync=debug" cargo run

# Modo silencioso: apenas erros e avisos
RUST_LOG="warn" cargo run

# Verboso: todos os internos do client em nível debug
RUST_LOG="debug" cargo run
Durante o desligamento ou desconexão, o cliente automaticamente rebaixa erros de sincronização do nível error para debug para reduzir ruído. Isso significa que você não verá logs de erro espúrios quando o cliente estiver se desconectando intencionalmente.

Executando com Docker

Você também pode executar o bot usando Docker em vez de compilar localmente:
docker build -t whatsapp-rust .
docker run -v ./data:/data whatsapp-rust
Os dados da sessão são armazenados no diretório /data dentro do contêiner. Monte um volume para persisti-los entre reinicializações. O contêiner é desligado graciosamente em docker stop — o bot se desconecta limpamente do WhatsApp antes de sair. Veja o guia de instalação para mais detalhes.

Benchmarking

O repositório inclui um exemplo de benchmark em examples/benchmark.rs que você pode usar para testes rápidos de desempenho em nível de integração. Ele usa um backend em memória e suporta uma URL de WebSocket personalizada via a variável de ambiente WHATSAPP_WS_URL:
cargo run --example benchmark --features danger-skip-tls-verify
O exemplo de benchmark requer a feature flag danger-skip-tls-verify porque foi projetado para uso com servidores de teste locais.
Para benchmarks de integração mais abrangentes com rastreamento de alocações, a suíte bench-integration mede cenários do mundo real (conectar, enviar, receber, reconectar) e reporta o tempo de relógio e contagens de alocação no heap por operação:
# Requer um servidor mock (por exemplo, Bartender)
MOCK_SERVER_URL="wss://127.0.0.1:8080/ws/chat" \
  cargo run -p bench-integration --release
Para benchmarks de protocolo de baixo nível, o crate wacore inclui uma suíte de benchmarks com iai-callgrind que mede contagens de instruções para o pipeline completo de envio/recebimento (mensagens DM e em grupo com várias contagens de participantes), codificação do protocolo binário, operações do Signal Protocol e geração de tokens de reporte:
# Executa todos os benchmarks do wacore (requer valgrind e iai-callgrind-runner)
cargo bench --workspace
Veja a documentação dos benchmarks do wacore para detalhes sobre cada suíte, otimizações de alocação e integração com CI.

Próximos passos

Enviando mensagens

Aprenda sobre os diferentes tipos de mensagem e como enviá-los

Manipulação de mídia

Faça upload e download de imagens, vídeos e documentos

Gerenciamento de grupos

Crie e gerencie grupos do WhatsApp

Referência da API Client

Explore todos os métodos disponíveis do cliente