// 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 }); }