diff --git a/.env.example b/.env.example index 831e431..05ded31 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ LOGTYPE=console WEBHOOKFILES=true CONNECT_ON_STARTUP=true +TELEMETRY_ENABLED=true OS_NAME=Evolution GO diff --git a/README.md b/README.md index bc89966..3bbc3fe 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,18 @@ Evolution Go is a high-performance WhatsApp API built in Go, part of the [Evolut ### Docker (Recommended) +The easiest and fastest way to start the project with all its dependencies (PostgreSQL, RabbitMQ, MinIO) is by using our automated script: + ```bash git clone https://github.com/EvolutionAPI/evolution-go.git cd evolution-go -make docker-build -make docker-run + +# Runs the script that will prepare dependencies, variables, and start the containers +bash ./start.sh ``` +> **Note:** The `start.sh` script will initialize submodules, create your `.env` based on `.env.example` (adjusting hosts for Docker), and run `docker compose up -d --build`. + ### Local Development ```bash diff --git a/cmd/evolution-go/main.go b/cmd/evolution-go/main.go index ddc760e..6ada342 100644 --- a/cmd/evolution-go/main.go +++ b/cmd/evolution-go/main.go @@ -200,7 +200,7 @@ func setupRouter(db *gorm.DB, authDB *sql.DB, sqliteDB *sql.DB, config *config.C // NOVO: PollHandler usando PollService já inicializado no whatsmeowService (evita dupla inicialização) pollHandler := poll_handler.NewPollHandler(whatsmeowService.GetPollService(), loggerWrapper) - telemetry := telemetry.NewTelemetryService() + telemetry := telemetry.NewTelemetryService(config.TelemetryEnabled) r := gin.Default() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..53018b5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,86 @@ +version: '3.8' + +services: + evolution-go: + build: + context: . + dockerfile: Dockerfile + container_name: evolution-go + restart: unless-stopped + ports: + - "8080:8080" + env_file: + - .env + volumes: + - evolution_data:/app/dbdata + - evolution_logs:/app/logs + networks: + - evolution_network + depends_on: + - postgres + - rabbitmq + - minio + + postgres: + image: postgres:15-alpine + container_name: postgres + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: root + POSTGRES_DB: postgres + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./docker/examples/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql + networks: + - evolution_network + + rabbitmq: + image: rabbitmq:3-management-alpine + container_name: rabbitmq + restart: unless-stopped + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: admin + RABBITMQ_DEFAULT_VHOST: default + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq + networks: + - evolution_network + + minio: + image: minio/minio:latest + container_name: minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio_data:/data + networks: + - evolution_network + +volumes: + evolution_data: + driver: local + evolution_logs: + driver: local + postgres_data: + driver: local + rabbitmq_data: + driver: local + minio_data: + driver: local + +networks: + evolution_network: + driver: bridge diff --git a/pkg/config/config.go b/pkg/config/config.go index a7a540a..283a0c2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -60,6 +60,7 @@ type Config struct { EventIgnoreStatus bool QrcodeMaxCount int CheckUserExists bool + TelemetryEnabled bool // Logger configurations LogMaxSize int @@ -67,7 +68,6 @@ type Config struct { LogMaxAge int LogDirectory string LogCompress bool - } // EnsureDBExists connects to postgres (without the target database) and creates it if it doesn't exist. @@ -274,6 +274,7 @@ func Load() *Config { eventIgnoreStatus := os.Getenv(config_env.EVENT_IGNORE_STATUS) qrcodeMaxCount := os.Getenv(config_env.QRCODE_MAX_COUNT) checkUserExists := os.Getenv(config_env.CHECK_USER_EXISTS) + telemetryEnabled := os.Getenv(config_env.TELEMETRY_ENABLED) if checkUserExists == "" { checkUserExists = "true" @@ -372,7 +373,8 @@ func Load() *Config { EventIgnoreGroup: eventIgnoreGroup == "true", EventIgnoreStatus: eventIgnoreStatus == "true", QrcodeMaxCount: qrMaxCount, - CheckUserExists: checkUserExists != "false", // Default true, set to false to disable + CheckUserExists: checkUserExists != "false", // Default true, set to false to disable + TelemetryEnabled: telemetryEnabled != "false", // Default true, set to false to disable AmqpGlobalEvents: amqpGlobalEvents, AmqpSpecificEvents: amqpSpecificEvents, NatsUrl: natsUrl, diff --git a/pkg/config/env/env.go b/pkg/config/env/env.go index 0fbd936..cb855f9 100644 --- a/pkg/config/env/env.go +++ b/pkg/config/env/env.go @@ -44,6 +44,7 @@ const ( EVENT_IGNORE_STATUS = "EVENT_IGNORE_STATUS" QRCODE_MAX_COUNT = "QRCODE_MAX_COUNT" CHECK_USER_EXISTS = "CHECK_USER_EXISTS" + TELEMETRY_ENABLED = "TELEMETRY_ENABLED" // Logger configurations LOG_MAX_SIZE = "LOG_MAX_SIZE" diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 067a543..5df7f43 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -5,6 +5,7 @@ import ( "encoding/json" "log" "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -16,10 +17,16 @@ type TelemetryData struct { Timestamp time.Time `json:"timestamp"` } -type telemetryService struct{} +type telemetryService struct { + enabled bool +} func (t *telemetryService) TelemetryMiddleware() gin.HandlerFunc { return func(c *gin.Context) { + if !t.enabled { + c.Next() + return + } route := c.FullPath() go SendTelemetry(route) c.Next() @@ -51,12 +58,17 @@ func SendTelemetry(route string) { resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) if err != nil { - log.Println("Erro ao enviar telemetria:", err) + // Silence DNS lookup errors or connection refused as they are common in restricted environments + if !strings.Contains(err.Error(), "no such host") && !strings.Contains(err.Error(), "connection refused") { + log.Println("Erro ao enviar telemetria:", err) + } return } defer resp.Body.Close() } -func NewTelemetryService() TelemetryService { - return &telemetryService{} +func NewTelemetryService(enabled bool) TelemetryService { + return &telemetryService{ + enabled: enabled, + } } diff --git a/pkg/whatsmeow/service/whatsmeow.go b/pkg/whatsmeow/service/whatsmeow.go index 6af7174..c932a9e 100644 --- a/pkg/whatsmeow/service/whatsmeow.go +++ b/pkg/whatsmeow/service/whatsmeow.go @@ -1914,16 +1914,59 @@ func (w *whatsmeowService) CallWebhook(instance *instance_model.Instance, queueN if contains(subscriptions, "MESSAGE") { w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s", instance.Id, eventType) w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else { + // Check if message is from group or newsletter and if user is subscribed to it + if dataMap, ok := data["data"].(map[string]interface{}); ok { + if infoMap, ok := dataMap["Info"].(map[string]interface{}); ok { + if chat, ok := infoMap["Chat"].(string); ok { + if strings.HasSuffix(chat, "@g.us") && contains(subscriptions, "GROUP") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Group)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else if strings.HasSuffix(chat, "@newsletter") && contains(subscriptions, "NEWSLETTER") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Newsletter)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } + } + } + } } case "SendMessage": if contains(subscriptions, "SEND_MESSAGE") { w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s", instance.Id, eventType) w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else { + // Check if message is to group or newsletter and if user is subscribed to it + if dataMap, ok := data["data"].(map[string]interface{}); ok { + if infoMap, ok := dataMap["Info"].(map[string]interface{}); ok { + if chat, ok := infoMap["Chat"].(string); ok { + if strings.HasSuffix(chat, "@g.us") && contains(subscriptions, "GROUP") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Group)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else if strings.HasSuffix(chat, "@newsletter") && contains(subscriptions, "NEWSLETTER") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Newsletter)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } + } + } + } } case "Receipt": if contains(subscriptions, "READ_RECEIPT") { w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s", instance.Id, eventType) w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else { + // Check if receipt is from group or newsletter and if user is subscribed to it + if dataMap, ok := data["data"].(map[string]interface{}); ok { + if chat, ok := dataMap["Chat"].(string); ok { + if strings.HasSuffix(chat, "@g.us") && contains(subscriptions, "GROUP") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Group)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } else if strings.HasSuffix(chat, "@newsletter") && contains(subscriptions, "NEWSLETTER") { + w.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Event received of type %s (Newsletter)", instance.Id, eventType) + w.sendToQueueOrWebhook(instance, queueName, jsonData) + } + } + } } case "Presence": if contains(subscriptions, "PRESENCE") { diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..343dbed --- /dev/null +++ b/start.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Output colors +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${BLUE}=== Initializing Evolution Go via Docker ===${NC}\n" + +# 1. Update git submodules (fixes empty folder error in whatsmeow-lib) +echo -e "${YELLOW}[1/4] Preparing dependencies (git submodules)...${NC}" +git submodule update --init --recursive +if [ $? -ne 0 ]; then + echo -e "${RED}Error initializing submodules. Please check if git is installed.${NC}" + exit 1 +fi +echo -e "${GREEN}Dependencies prepared successfully!${NC}\n" + +# 2. Create .env file if it doesn't exist +echo -e "${YELLOW}[2/4] Configuring environment variables...${NC}" +if [ ! -f .env ]; then + if [ -f .env.example ]; then + cp .env.example .env + + # Automatically adjust for Docker Compose + sed -i 's/localhost:5432/postgres:5432/g' .env + sed -i 's/localhost:5672/rabbitmq:5672/g' .env + sed -i 's/localhost:9000/minio:9000/g' .env + + echo -e "${GREEN}.env file created from .env.example and adjusted for Docker!${NC}\n" + else + echo -e "${RED}.env.example file not found!${NC}" + exit 1 + fi +else + echo -e "${GREEN}.env file already exists, keeping current configuration.${NC}\n" +fi + +# 3. Build and start containers via Docker Compose +echo -e "${YELLOW}[3/4] Building image and starting containers (This may take a few minutes)...${NC}" +if command -v docker-compose &> /dev/null; then + docker-compose up -d --build +elif docker compose version &> /dev/null; then + docker compose up -d --build +else + echo -e "${RED}Docker Compose not found. Please install Docker Compose first.${NC}" + exit 1 +fi + +if [ $? -ne 0 ]; then + echo -e "${RED}Error starting Docker containers.${NC}" + exit 1 +fi +echo -e "${GREEN}Containers started successfully!${NC}\n" + +# 4. Finalization +echo -e "${BLUE}=== All set! ===${NC}" +echo -e "Evolution Go and its dependencies are running in the background." +echo -e "\nServices available:" +echo -e "- Evolution Go API: ${GREEN}http://localhost:8080${NC}" +echo -e "- Swagger Docs: ${GREEN}http://localhost:8080/swagger/index.html${NC}" +echo -e "- Manager UI: ${GREEN}http://localhost:8080/manager/login${NC}" +echo -e "- RabbitMQ Admin: ${GREEN}http://localhost:15672${NC} (admin/admin)" +echo -e "- MinIO Console: ${GREEN}http://localhost:9001${NC} (minioadmin/minioadmin)" +echo -e "\nTo view API logs, run:" +echo -e "${YELLOW}docker compose logs -f evolution-go${NC}"