// ZapApi.cs - cliente C# da API de WhatsApp da PluguiZap (rotas /instances/{ID}/token/{TOKEN}).
// Sem pacotes extras: HttpClient e System.Text.Json. .NET 6+.
//
// var zap = new ZapApi("SEU_ID", "SEU_TOKEN", "SEU_CLIENT_TOKEN");
// var r = await zap.TextoAsync("79999998888", "Seu pedido saiu para entrega");
// Console.WriteLine(r.Ok ? r.Campo("messageId") : r.Erro);
// await zap.ArquivoAsync("79999998888", @"C:\Boletos\1042.pdf", "Segue o boleto");
//
// Dica: crie UMA instância de ZapApi e reaproveite (o HttpClient deve viver a aplicação inteira).
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public sealed class ZapApi
{
public const string UrlPadrao = "https://api.pluguizap.com.br";
private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(60) };
private readonly string _base;
private readonly string _clientToken;
public ZapApi(string instanciaId, string token, string clientToken, string urlBase = UrlPadrao)
{
_base = urlBase.TrimEnd('/') + "/instances/" + instanciaId + "/token/" + token;
_clientToken = clientToken;
}
/// Resultado: código HTTP e o JSON devolvido.
public sealed record Resposta(int Http, string Json)
{
public bool Ok => Http >= 200 && Http < 300;
public JsonElement Dados => string.IsNullOrWhiteSpace(Json) ? default : JsonDocument.Parse(Json).RootElement;
public string? Campo(string nome) =>
Dados.ValueKind == JsonValueKind.Object && Dados.TryGetProperty(nome, out var v)
? (v.ValueKind == JsonValueKind.String ? v.GetString() : v.GetRawText()) : null;
public string? Erro => Ok ? null : (Campo("error") ?? "HTTP " + Http);
}
public static string Tel(string t)
{
if (t.Contains("-group") || t.Contains('@')) return t;
var d = Regex.Replace(t, @"\D", "");
return d.Length is 10 or 11 ? "55" + d : d;
}
/// Chama qualquer rota. corpo = qualquer objeto (vira JSON) ou null.
public async Task ReqAsync(string metodo, string rota, object? corpo = null)
{
using var pedido = new HttpRequestMessage(new HttpMethod(metodo), _base + "/" + rota);
pedido.Headers.Add("Client-Token", _clientToken);
if (corpo != null)
pedido.Content = new StringContent(JsonSerializer.Serialize(corpo), Encoding.UTF8, "application/json");
try
{
using var r = await Http.SendAsync(pedido);
return new Resposta((int)r.StatusCode, await r.Content.ReadAsStringAsync());
}
catch (Exception e)
{
return new Resposta(0, JsonSerializer.Serialize(new { error = "Falha de conexão: " + e.Message }));
}
}
// ---------- conexão ----------
public Task StatusAsync() => ReqAsync("GET", "status");
public async Task ConectadoAsync() => (await StatusAsync()).Campo("connected") == "true";
/// Campo("value") = "data:image/png;base64,..."
public Task QrCodeAsync() => ReqAsync("GET", "qr-code/image");
public Task CodigoTelefoneAsync(string telefone) => ReqAsync("GET", "phone-code/" + Tel(telefone));
public Task ReiniciarAsync() => ReqAsync("GET", "restart");
public Task DesconectarAsync() => ReqAsync("GET", "disconnect");
// ---------- mensagens ----------
public Task TextoAsync(string telefone, string mensagem) =>
ReqAsync("POST", "send-text", new { phone = Tel(telefone), message = mensagem });
public Task ImagemAsync(string telefone, string imagem, string legenda = "") =>
ReqAsync("POST", "send-image", new { phone = Tel(telefone), image = imagem, caption = legenda });
public Task DocumentoAsync(string telefone, string documento, string nomeArquivo = "arquivo.pdf", string legenda = "")
{
var ext = Path.GetExtension(nomeArquivo).TrimStart('.').ToLowerInvariant();
return ReqAsync("POST", "send-document/" + (ext == "" ? "pdf" : ext), new { phone = Tel(telefone), document = documento, fileName = nomeArquivo, caption = legenda });
}
/// Envia um arquivo do disco. Imagens vão como imagem; o resto como documento.
public async Task ArquivoAsync(string telefone, string caminho, string legenda = "")
{
var ext = Path.GetExtension(caminho).TrimStart('.').ToLowerInvariant();
var mime = ext switch
{
"pdf" => "application/pdf", "jpg" or "jpeg" => "image/jpeg", "png" => "image/png", "webp" => "image/webp",
"xml" => "application/xml", "txt" => "text/plain", "zip" => "application/zip", _ => "application/octet-stream",
};
var dados = "data:" + mime + ";base64," + Convert.ToBase64String(await File.ReadAllBytesAsync(caminho));
return mime.StartsWith("image/") ? await ImagemAsync(telefone, dados, legenda) : await DocumentoAsync(telefone, dados, Path.GetFileName(caminho), legenda);
}
public Task EnqueteAsync(string telefone, string pergunta, params string[] opcoes) =>
ReqAsync("POST", "send-poll", new { phone = Tel(telefone), message = pergunta, poll = opcoes.Select(o => new { name = o }), pollMaxOptions = 1 });
/// Chega como menu numerado; a resposta volta no webhook em buttonsResponseMessage.buttonId ("1", "2"...).
public Task BotoesAsync(string telefone, string mensagem, params string[] botoes) =>
ReqAsync("POST", "send-button-list", new { phone = Tel(telefone), message = mensagem,
buttonList = new { buttons = botoes.Select((b, i) => new { id = (i + 1).ToString(), label = b }) } });
public Task LocalizacaoAsync(string telefone, double latitude, double longitude, string nome = "", string endereco = "") =>
ReqAsync("POST", "send-location", new { phone = Tel(telefone), latitude, longitude, title = nome, address = endereco });
public Task MarcarLidaAsync(string telefone, string messageId) =>
ReqAsync("POST", "read-message", new { phone = Tel(telefone), messageId });
// ---------- contatos e webhook ----------
public async Task TemWhatsappAsync(string telefone) => (await ReqAsync("GET", "phone-exists/" + Tel(telefone))).Campo("exists") == "true";
public Task WebhookTodosAsync(string url) => ReqAsync("PUT", "update-every-webhooks", new { value = url });
}