Initial project import
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package com.seconectai.condocontab.integracao;
|
||||
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.PasswordAuthentication;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public final class EmailClient {
|
||||
public void enviar(Map<String, String> smtp, String destino, String assunto, String texto) throws MessagingException {
|
||||
if (smtp == null || smtp.getOrDefault("smtp_host", "").isBlank()) return;
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", smtp.get("smtp_host"));
|
||||
props.put("mail.smtp.port", smtp.getOrDefault("smtp_port", "587"));
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.starttls.enable", smtp.getOrDefault("smtp_starttls", "true"));
|
||||
String user = smtp.getOrDefault("smtp_user", "");
|
||||
String pass = smtp.getOrDefault("smtp_password", "");
|
||||
Session session = Session.getInstance(props, new jakarta.mail.Authenticator() {
|
||||
@Override protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(user, pass);
|
||||
}
|
||||
});
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(smtp.getOrDefault("smtp_from", user)));
|
||||
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destino));
|
||||
message.setSubject(assunto == null ? "Aviso" : assunto, "UTF-8");
|
||||
message.setText(texto == null ? "" : texto, "UTF-8");
|
||||
Transport.send(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.seconectai.condocontab.integracao;
|
||||
|
||||
import com.seconectai.condocontab.config.AppConfig;
|
||||
import com.seconectai.condocontab.util.Json;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class EvolutionApiClient {
|
||||
private final String globalApiKey;
|
||||
private final String defaultBaseUrl;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public EvolutionApiClient(AppConfig config) {
|
||||
this.globalApiKey = config.evolutionGlobalApiKey();
|
||||
this.defaultBaseUrl = config.evolutionApiUrl();
|
||||
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
|
||||
}
|
||||
|
||||
public CriacaoInstancia criarInstancia(String baseUrl, String instanceName, String token, String numero) throws IOException, InterruptedException {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
baseUrl = defaultBaseUrl;
|
||||
}
|
||||
if (mockMode(baseUrl)) {
|
||||
String qr = Base64.getEncoder().encodeToString(("QR:" + instanceName).getBytes(StandardCharsets.UTF_8));
|
||||
return new CriacaoInstancia(instanceName, token, qr, true);
|
||||
}
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("instanceName", instanceName);
|
||||
payload.put("integration", "WHATSAPP-BAILEYS");
|
||||
payload.put("qrcode", true);
|
||||
payload.put("number", onlyDigits(numero));
|
||||
HttpRequest req = HttpRequest.newBuilder(URI.create(clean(baseUrl) + "/instance/create"))
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("apikey", globalApiKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(Json.write(payload)))
|
||||
.build();
|
||||
HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
|
||||
throw new IOException("Evolution API retornou HTTP " + resp.statusCode() + ": " + resp.body());
|
||||
}
|
||||
return new CriacaoInstancia(instanceName, firstNonBlank(extractJsonString(resp.body(), "hash"), token), qrBase64(resp.body()), false);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> conectar(String baseUrl, String instanceName, String numero) throws IOException, InterruptedException {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
baseUrl = defaultBaseUrl;
|
||||
}
|
||||
if (mockMode(baseUrl)) {
|
||||
String qr = Base64.getEncoder().encodeToString(("QR:" + instanceName).getBytes(StandardCharsets.UTF_8));
|
||||
return Map.of("instance_name", instanceName, "qrcode_base64", qr, "mock", true);
|
||||
}
|
||||
String url = clean(baseUrl) + "/instance/connect/" + instanceName;
|
||||
String digits = onlyDigits(numero);
|
||||
if (!digits.isBlank()) {
|
||||
url += "?number=" + digits;
|
||||
}
|
||||
HttpResponse<String> resp = sendNoBody("GET", url, globalApiKey);
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("instance_name", instanceName);
|
||||
out.put("pairing_code", extractJsonString(resp.body(), "pairingCode"));
|
||||
out.put("qr_code_payload", extractJsonString(resp.body(), "code"));
|
||||
out.put("qr_code_base64", qrBase64(resp.body()));
|
||||
out.put("raw", resp.body());
|
||||
return out;
|
||||
}
|
||||
|
||||
public Map<String, Object> logout(String baseUrl, String instanceName) throws IOException, InterruptedException {
|
||||
return instanceAction("DELETE", baseUrl, "/instance/logout/" + instanceName, instanceName);
|
||||
}
|
||||
|
||||
public Map<String, Object> excluirInstancia(String baseUrl, String instanceName) throws IOException, InterruptedException {
|
||||
return instanceAction("DELETE", baseUrl, "/instance/delete/" + instanceName, instanceName);
|
||||
}
|
||||
|
||||
public Map<String, Object> reiniciar(String baseUrl, String instanceName) throws IOException, InterruptedException {
|
||||
return instanceAction("POST", baseUrl, "/instance/restart/" + instanceName, instanceName);
|
||||
}
|
||||
|
||||
public Map<String, Object> estado(String baseUrl, String instanceName) throws IOException, InterruptedException {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
baseUrl = defaultBaseUrl;
|
||||
}
|
||||
if (mockMode(baseUrl)) {
|
||||
return Map.of("instance_name", instanceName, "state", "mock");
|
||||
}
|
||||
HttpResponse<String> resp = sendNoBody("GET", clean(baseUrl) + "/instance/connectionState/" + instanceName, globalApiKey);
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("instance_name", instanceName);
|
||||
out.put("state", firstNonBlank(extractJsonString(resp.body(), "state"), extractJsonString(resp.body(), "connectionStatus")));
|
||||
out.put("raw", resp.body());
|
||||
return out;
|
||||
}
|
||||
|
||||
private Map<String, Object> instanceAction(String method, String baseUrl, String path, String instanceName) throws IOException, InterruptedException {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
baseUrl = defaultBaseUrl;
|
||||
}
|
||||
if (mockMode(baseUrl)) {
|
||||
return Map.of("instance_name", instanceName, "ok", true, "mock", true);
|
||||
}
|
||||
HttpResponse<String> resp = sendNoBody(method, clean(baseUrl) + path, globalApiKey);
|
||||
return Map.of("instance_name", instanceName, "ok", true, "raw", resp.body());
|
||||
}
|
||||
|
||||
private HttpResponse<String> sendNoBody(String method, String url, String apiKey) throws IOException, InterruptedException {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url))
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.header("apikey", apiKey);
|
||||
if ("GET".equals(method)) {
|
||||
builder.GET();
|
||||
} else if ("DELETE".equals(method)) {
|
||||
builder.DELETE();
|
||||
} else {
|
||||
builder.method(method, HttpRequest.BodyPublishers.noBody());
|
||||
}
|
||||
HttpResponse<String> resp = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
|
||||
throw new IOException("Evolution API retornou HTTP " + resp.statusCode() + ": " + resp.body());
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
public String enviarTexto(String baseUrl, String instanceName, String token, String numero, String texto) throws IOException, InterruptedException {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
baseUrl = defaultBaseUrl;
|
||||
}
|
||||
if (mockMode(baseUrl)) return "mock";
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("number", onlyDigits(numero));
|
||||
payload.put("text", texto);
|
||||
HttpRequest req = HttpRequest.newBuilder(URI.create(clean(baseUrl) + "/message/sendText/" + instanceName))
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("apikey", globalApiKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(Json.write(payload)))
|
||||
.build();
|
||||
HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
|
||||
throw new IOException("Evolution API retornou HTTP " + resp.statusCode() + ": " + resp.body());
|
||||
}
|
||||
return resp.body();
|
||||
}
|
||||
|
||||
private boolean mockMode(String baseUrl) {
|
||||
return globalApiKey == null || globalApiKey.isBlank() || baseUrl == null || baseUrl.isBlank() || baseUrl.startsWith("mock://");
|
||||
}
|
||||
|
||||
private String clean(String baseUrl) {
|
||||
return baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
}
|
||||
|
||||
public static String onlyDigits(String raw) {
|
||||
return raw == null ? "" : raw.replaceAll("\\D", "");
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String fallback) {
|
||||
return first == null || first.isBlank() ? fallback : first;
|
||||
}
|
||||
|
||||
private String qrBase64(String responseBody) throws IOException {
|
||||
String image = firstNonBlank(extractJsonString(responseBody, "base64"), extractJsonString(responseBody, "qrcode"));
|
||||
if (looksLikeImageBase64(image)) {
|
||||
return stripDataImagePrefix(image);
|
||||
}
|
||||
String payload = extractJsonString(responseBody, "code");
|
||||
if (payload == null || payload.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
var matrix = new QRCodeWriter().encode(payload, BarcodeFormat.QR_CODE, 320, 320);
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
MatrixToImageWriter.writeToStream(matrix, "PNG", out);
|
||||
return Base64.getEncoder().encodeToString(out.toByteArray());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Falha ao gerar QR Code local", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean looksLikeImageBase64(String value) {
|
||||
if (value == null || value.isBlank()) return false;
|
||||
String clean = stripDataImagePrefix(value);
|
||||
return clean.startsWith("iVBOR") || clean.startsWith("/9j/") || clean.startsWith("PHN2Zy");
|
||||
}
|
||||
|
||||
private String stripDataImagePrefix(String value) {
|
||||
if (value == null) return null;
|
||||
int comma = value.indexOf(',');
|
||||
return value.startsWith("data:image/") && comma > 0 ? value.substring(comma + 1) : value;
|
||||
}
|
||||
|
||||
private String extractJsonString(String raw, String key) {
|
||||
String marker = "\"" + key + "\"";
|
||||
int p = raw.indexOf(marker);
|
||||
if (p < 0) return null;
|
||||
int colon = raw.indexOf(':', p);
|
||||
int first = raw.indexOf('"', colon + 1);
|
||||
int second = raw.indexOf('"', first + 1);
|
||||
if (first < 0 || second < 0) return null;
|
||||
return raw.substring(first + 1, second);
|
||||
}
|
||||
|
||||
public record CriacaoInstancia(String instanceName, String token, String qrcodeBase64, boolean mock) {}
|
||||
}
|
||||
Reference in New Issue
Block a user