texto('5579999998888', 'Olá!'); * $zap->documento('5579999998888', 'https://site.com/boleto.pdf', 'boleto.pdf'); * $zap->status(); // ['connected' => true, ...] * $zap->qrCode(); // ['value' => 'data:image/png;base64,...'] (para ) */ class ZapApi { private string $base; private string $clientToken; private int $timeout; public function __construct( string $instanceId, string $token, string $clientToken = '', string $urlBase = 'https://api.pluguizap.com.br', int $timeout = 60 ) { $this->base = rtrim($urlBase, '/') . '/instances/' . rawurlencode($instanceId) . '/token/' . rawurlencode($token); $this->clientToken = $clientToken; $this->timeout = $timeout; } // ---------- Conexão ---------- public function status(): array { return $this->req('GET', 'status'); } public function conectado(): bool { return !empty($this->status()['connected']); } public function qrCode(): array { return $this->req('GET', 'qr-code/image'); } public function codigoTelefone(string $telefone): array { return $this->req('GET', 'phone-code/' . preg_replace('/\D/', '', $telefone)); } public function desconectar(): array { return $this->req('GET', 'disconnect'); } public function reiniciar(): array { return $this->req('GET', 'restart'); } // ---------- Envio ---------- public function texto(string $telefone, string $mensagem, int $digitandoSeg = 0): array { $b = ['phone' => $this->tel($telefone), 'message' => $mensagem]; if ($digitandoSeg > 0) { $b['delayTyping'] = min(15, $digitandoSeg); } return $this->req('POST', 'send-text', $b); } /** $imagem: URL ou data URL base64 */ public function imagem(string $telefone, string $imagem, string $legenda = ''): array { return $this->req('POST', 'send-image', ['phone' => $this->tel($telefone), 'image' => $imagem, 'caption' => $legenda]); } /** $documento: URL ou data URL base64. A extensão sai do nome do arquivo. */ public function documento(string $telefone, string $documento, string $nomeArquivo = 'arquivo.pdf', string $legenda = ''): array { $ext = strtolower(pathinfo($nomeArquivo, PATHINFO_EXTENSION) ?: 'pdf'); return $this->req('POST', 'send-document/' . $ext, [ 'phone' => $this->tel($telefone), 'document' => $documento, 'fileName' => $nomeArquivo, 'caption' => $legenda, ]); } /** Envia um arquivo do disco do servidor (converte para base64). */ public function arquivoLocal(string $telefone, string $caminho, string $legenda = ''): array { if (!is_file($caminho)) { return ['error' => "Arquivo não encontrado: $caminho"]; } $mime = mime_content_type($caminho) ?: 'application/octet-stream'; $data = 'data:' . $mime . ';base64,' . base64_encode(file_get_contents($caminho)); if (strpos($mime, 'image/') === 0) { return $this->imagem($telefone, $data, $legenda); } return $this->documento($telefone, $data, basename($caminho), $legenda); } public function audio(string $telefone, string $audio): array { return $this->req('POST', 'send-audio', ['phone' => $this->tel($telefone), 'audio' => $audio]); } public function video(string $telefone, string $video, string $legenda = ''): array { return $this->req('POST', 'send-video', ['phone' => $this->tel($telefone), 'video' => $video, 'caption' => $legenda]); } /** Enquete. $opcoes = ['Açaí', 'Cupuaçu']. $umaOpcao=true: cada pessoa escolhe só uma. */ public function enquete(string $telefone, string $pergunta, array $opcoes, bool $umaOpcao = true): array { $b = ['phone' => $this->tel($telefone), 'message' => $pergunta, 'poll' => array_map(fn($o) => ['name' => (string) $o], $opcoes)]; if ($umaOpcao) { $b['pollMaxOptions'] = 1; } return $this->req('POST', 'send-poll', $b); } /** Botões (viram menu numerado). $botoes = ['sim' => 'Sim', 'nao' => 'Não'] ou ['Sim', 'Não'] */ public function botoes(string $telefone, string $mensagem, array $botoes): array { $lista = []; foreach ($botoes as $id => $rotulo) { $lista[] = ['id' => (string) (is_int($id) ? $id + 1 : $id), 'label' => (string) $rotulo]; } return $this->req('POST', 'send-button-list', ['phone' => $this->tel($telefone), 'message' => $mensagem, 'buttonList' => ['buttons' => $lista]]); } /** Lista de opções (vira menu numerado). $opcoes = [['id' => 'p1', 'title' => 'Pizza', 'description' => '8 fatias'], ...] */ public function lista(string $telefone, string $mensagem, string $titulo, array $opcoes, string $rotuloBotao = 'Ver opções'): array { return $this->req('POST', 'send-option-list', [ 'phone' => $this->tel($telefone), 'message' => $mensagem, 'optionList' => ['title' => $titulo, 'buttonLabel' => $rotuloBotao, 'options' => $opcoes], ]); } /** Link com prévia */ public function link(string $telefone, string $mensagem, string $url, string $titulo, string $descricao = '', string $imagem = ''): array { return $this->req('POST', 'send-link', [ 'phone' => $this->tel($telefone), 'message' => $mensagem, 'linkUrl' => $url, 'title' => $titulo, 'linkDescription' => $descricao, 'image' => $imagem, ]); } public function localizacao(string $telefone, float $lat, float $lng, string $titulo = '', string $endereco = ''): array { return $this->req('POST', 'send-location', [ 'phone' => $this->tel($telefone), 'latitude' => $lat, 'longitude' => $lng, 'title' => $titulo, 'address' => $endereco, ]); } public function contato(string $telefone, string $nome, string $telefoneContato): array { return $this->req('POST', 'send-contact', ['phone' => $this->tel($telefone), 'contactName' => $nome, 'contactPhone' => $telefoneContato]); } public function reagir(string $telefone, string $messageId, string $emoji = '👍'): array { return $this->req('POST', 'send-reaction', ['phone' => $this->tel($telefone), 'messageId' => $messageId, 'reaction' => $emoji]); } /** Apaga uma mensagem. $minha=true para mensagens enviadas por você. */ public function apagar(string $telefone, string $messageId, bool $minha = true, bool $soParaMim = false): array { $q = http_build_query([ 'messageId' => $messageId, 'phone' => $this->tel($telefone), 'owner' => $minha ? 'true' : 'false', 'deleteForMe' => $soParaMim ? 'true' : 'false', ]); return $this->req('DELETE', 'messages?' . $q); } // ---------- Grupos (IDs no formato "120363...-group") ---------- public function criarGrupo(string $nome, array $telefones, bool $convidarQuemFalhar = true): array { return $this->req('POST', 'create-group', [ 'groupName' => $nome, 'phones' => array_map([$this, 'tel'], $telefones), 'autoInvite' => $convidarQuemFalhar, ]); } public function adicionarAoGrupo(string $grupo, array $telefones): array { return $this->grupoAcao('add-participant', $grupo, $telefones, true); } public function removerDoGrupo(string $grupo, array $telefones): array { return $this->grupoAcao('remove-participant', $grupo, $telefones); } public function promoverAdmin(string $grupo, array $telefones): array { return $this->grupoAcao('add-admin', $grupo, $telefones); } public function removerAdmin(string $grupo, array $telefones): array { return $this->grupoAcao('remove-admin', $grupo, $telefones); } public function renomearGrupo(string $grupo, string $nome): array { return $this->req('POST', 'update-group-name', ['groupId' => $grupo, 'groupName' => $nome]); } public function descricaoGrupo(string $grupo, string $texto): array { return $this->req('POST', 'update-group-description', ['groupId' => $grupo, 'groupDescription' => $texto]); } public function fotoGrupo(string $grupo, string $imagem): array { return $this->req('POST', 'update-group-photo', ['groupId' => $grupo, 'groupPhoto' => $imagem]); } public function sairDoGrupo(string $grupo): array { return $this->req('POST', 'leave-group', ['groupId' => $grupo]); } public function dadosGrupo(string $grupo): array { return $this->req('GET', 'group-metadata/' . rawurlencode($grupo)); } public function linkConvite(string $grupo): array { return $this->req('GET', 'group-invitation-link/' . rawurlencode($grupo)); } public function grupos(int $pagina = 1, int $porPagina = 50): array { return $this->req('GET', "groups?page=$pagina&pageSize=$porPagina"); } private function grupoAcao(string $rota, string $grupo, array $telefones, bool $autoInvite = false): array { $b = ['groupId' => $grupo, 'phones' => array_map([$this, 'tel'], $telefones)]; if ($autoInvite) { $b['autoInvite'] = true; } return $this->req('POST', $rota, $b); } public function temWhatsapp(string $telefone): bool { $r = $this->req('GET', 'phone-exists/' . $this->tel($telefone)); return !empty($r['exists']); } /** Link da foto do perfil (expira em alguns dias) ou null se não houver foto. */ public function fotoPerfil(string $telefone): ?string { $r = $this->req('GET', 'profile-picture/' . $this->tel($telefone)); return $r['link'] ?? null; } public function marcarLida(string $telefone, string $messageId): array { return $this->req('POST', 'read-message', ['phone' => $telefone, 'messageId' => $messageId]); } // ---------- Webhooks ---------- public function webhookTodos(string $url): array { return $this->req('PUT', 'update-every-webhooks', ['value' => $url]); } public function webhookRecebidas(string $url): array { return $this->req('PUT', 'update-webhook-received', ['value' => $url]); } public function webhookStatus(string $url): array { return $this->req('PUT', 'update-webhook-message-status', ['value' => $url]); } public function webhookConectou(string $url): array { return $this->req('PUT', 'update-webhook-connected', ['value' => $url]); } public function webhookDesconectou(string $url): array { return $this->req('PUT', 'update-webhook-disconnected', ['value' => $url]); } public function webhookEntrega(string $url): array { return $this->req('PUT', 'update-webhook-delivery', ['value' => $url]); } /** Envia ao webhook de recebidas também as mensagens que o atendente manda pelo celular */ public function avisarEnviadasPeloCelular(bool $ligar = true): array { return $this->req('PUT', 'update-notify-sent-by-me', ['notifySentByMe' => $ligar]); } // ---------- Interno ---------- /** Só dígitos; acrescenta 55 em números brasileiros sem DDI. Grupos ("...-group") e "@lid" passam direto. */ private function tel(string $t): string { if (strpos($t, '-group') !== false || strpos($t, '@') !== false) { return $t; } $d = preg_replace('/\D/', '', $t); return (strlen($d) === 10 || strlen($d) === 11) ? '55' . $d : $d; } private function req(string $metodo, string $rota, ?array $corpo = null): array { $ch = curl_init($this->base . '/' . $rota); $headers = ['Accept: application/json']; if ($this->clientToken !== '') { $headers[] = 'Client-Token: ' . $this->clientToken; } if ($corpo !== null) { $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($corpo, JSON_UNESCAPED_UNICODE)); } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $metodo, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_CONNECTTIMEOUT => 8, ]); $resp = curl_exec($ch); $erro = curl_error($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($resp === false) { return ['error' => 'Servidor de WhatsApp inacessível: ' . $erro]; } $json = json_decode($resp, true); if (!is_array($json)) { return ['error' => "Resposta inválida (HTTP $http)"]; } return $json; } }