package instance_service import ( "bufio" "context" "encoding/base64" "encoding/json" "errors" "fmt" "os" "path/filepath" "slices" "sort" "strings" "time" "agentdeck-whatsapp-service/pkg/config" instance_model "agentdeck-whatsapp-service/pkg/instance/model" instance_repository "agentdeck-whatsapp-service/pkg/instance/repository" event_types "agentdeck-whatsapp-service/pkg/internal/event_types" logger_wrapper "agentdeck-whatsapp-service/pkg/logger" "agentdeck-whatsapp-service/pkg/utils" whatsmeow_service "agentdeck-whatsapp-service/pkg/whatsmeow/service" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types" ) type InstanceService interface { Create(data *CreateStruct) (*instance_model.Instance, error) Connect(data *ConnectStruct, instance *instance_model.Instance) (*instance_model.Instance, string, string, error) Reconnect(instance *instance_model.Instance) error Disconnect(instance *instance_model.Instance) (*instance_model.Instance, error) Logout(instance *instance_model.Instance) (*instance_model.Instance, error) Status(instance *instance_model.Instance) (*StatusStruct, error) GetQr(instance *instance_model.Instance) (*QrcodeStruct, error) Pair(data *PairStruct, instance *instance_model.Instance) (*PairReturnStruct, error) GetAll() ([]*instance_model.Instance, error) Info(instanceId string) (*instance_model.Instance, error) Delete(id string) error SetProxy(id string, proxyConfig *ProxyConfig) error SetProxyFromStruct(id string, data *SetProxyStruct) error RemoveProxy(id string) error ForceReconnect(instanceId string, number string) error GetInstanceByToken(token string) (*instance_model.Instance, error) GetLogs(instanceId string, startDate, endDate time.Time, level string, limit int) ([]logger_wrapper.LogEntry, error) GetAdvancedSettings(instanceId string) (*instance_model.AdvancedSettings, error) UpdateAdvancedSettings(instanceId string, settings *instance_model.AdvancedSettings) error } type instances struct { instanceRepository instance_repository.InstanceRepository config *config.Config killChannel map[string](chan bool) clientPointer map[string]*whatsmeow.Client whatsmeowService whatsmeow_service.WhatsmeowService loggerWrapper *logger_wrapper.LoggerManager } type ProxyConfig struct { Protocol string `json:"protocol,omitempty"` Port string `json:"port"` Password string `json:"password"` Username string `json:"username"` Host string `json:"host"` } type CreateStruct struct { InstanceId string `json:"instanceId"` Name string `json:"name"` Token string `json:"token"` Proxy *ProxyConfig `json:"proxy"` AdvancedSettings *instance_model.AdvancedSettings `json:"advancedSettings"` } type ConnectStruct struct { WebhookUrl string `json:"webhookUrl"` Subscribe []string `json:"subscribe"` Immediate bool `json:"immediate"` Phone string `json:"phone"` RabbitmqEnable string `json:"rabbitmqEnable"` WebSocketEnable string `json:"websocketEnable"` NatsEnable string `json:"natsEnable"` } type StatusStruct struct { Connected bool LoggedIn bool myJid *types.JID Name string } type QrcodeStruct struct { Qrcode string `json:"qrcode"` Code string `json:"code"` // Passkey ceremony fields. Populated when the account requires a WebAuthn // passkey to finish linking (no QR to scan at that point). The manager uses // PasskeyStage to switch its UI and PasskeyOpenUrl for the // "Abrir WhatsApp Web" button that launches the passkey ceremony. PasskeyStage string `json:"passkeyStage,omitempty"` PasskeyOpenURL string `json:"passkeyOpenUrl,omitempty"` PasskeyCode string `json:"passkeyCode,omitempty"` } type PairStruct struct { Subscribe []string `json:"subscribe"` Phone string `json:"phone"` } type PairReturnStruct struct { PairingCode string } type SetProxyStruct struct { Protocol string `json:"protocol,omitempty"` Host string `json:"host" validate:"required"` Port string `json:"port" validate:"required"` Username string `json:"username"` Password string `json:"password"` } type ForceReconnectStruct struct { Number string `json:"number"` } func (i *instances) ensureClientConnected(instanceId string) (*whatsmeow.Client, error) { logger := i.loggerWrapper.GetLogger(instanceId) client := i.clientPointer[instanceId] logger.LogInfo("[%s] Checking client connection status - Client exists: %v", instanceId, client != nil) if client == nil { logger.LogInfo("[%s] No client found, attempting to start new instance", instanceId) err := i.whatsmeowService.StartInstance(instanceId) if err != nil { logger.LogError("[%s] Failed to start instance: %v", instanceId, err) return nil, errors.New("no active session found") } logger.LogInfo("[%s] Instance started, waiting 2 seconds...", instanceId) time.Sleep(2 * time.Second) client = i.clientPointer[instanceId] logger.LogInfo("[%s] Checking new client - Exists: %v, Connected: %v", instanceId, client != nil, client != nil && client.IsConnected()) if client == nil || !client.IsConnected() { logger.LogError("[%s] New client validation failed - Exists: %v, Connected: %v", instanceId, client != nil, client != nil && client.IsConnected()) return nil, errors.New("no active session found") } } else if !client.IsConnected() { logger.LogError("[%s] Existing client is disconnected - Connected status: %v", instanceId, client.IsConnected()) return nil, errors.New("client disconnected") } logger.LogInfo("[%s] Client successfully validated - Connected: %v", instanceId, client.IsConnected()) return client, nil } func (i instances) Create(data *CreateStruct) (*instance_model.Instance, error) { if data.Proxy != nil { data.Proxy.Protocol = utils.NormalizeProxyProtocol(data.Proxy.Protocol, data.Proxy.Port) } proxyJson, err := json.Marshal(data.Proxy) if err != nil { return nil, err } findInstance, _ := i.instanceRepository.GetInstanceByName(data.Name) if findInstance != nil { return nil, fmt.Errorf("instance already exists") } instance := instance_model.Instance{ Id: data.InstanceId, Name: data.Name, Token: data.Token, OsName: i.config.OsName, Proxy: string(proxyJson), Connected: false, ClientName: i.config.ClientName, } // Set advanced settings if provided if data.AdvancedSettings != nil { instance.AlwaysOnline = data.AdvancedSettings.AlwaysOnline instance.RejectCall = data.AdvancedSettings.RejectCall instance.MsgRejectCall = data.AdvancedSettings.MsgRejectCall instance.ReadMessages = data.AdvancedSettings.ReadMessages instance.IgnoreGroups = data.AdvancedSettings.IgnoreGroups instance.IgnoreStatus = data.AdvancedSettings.IgnoreStatus } createdInstance, err := i.instanceRepository.Create(instance) if err != nil { return nil, err } return createdInstance, nil } func (i instances) Connect(data *ConnectStruct, instance *instance_model.Instance) (*instance_model.Instance, string, string, error) { var subscribedEvents []string i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Processing subscribe events: %v", instance.Id, data.Subscribe) if len(data.Subscribe) == 0 { subscribedEvents = append(subscribedEvents, event_types.MESSAGE) } else if len(data.Subscribe) > 0 && data.Subscribe[0] == "ALL" { for _, event := range event_types.AllEventTypes { subscribedEvents = append(subscribedEvents, event) } } else { for _, arg := range data.Subscribe { if !event_types.IsEventType(arg) { i.loggerWrapper.GetLogger(instance.Id).LogWarn("[%s] Message type discarded '%s'", instance.Id, arg) continue } subscribedEvents = append(subscribedEvents, arg) } } eventString := strings.Join(subscribedEvents, ",") instance.Events = eventString instance.Webhook = data.WebhookUrl instance.RabbitmqEnable = data.RabbitmqEnable instance.NatsEnable = data.NatsEnable instance.WebSocketEnable = data.WebSocketEnable err := i.instanceRepository.Update(instance) if err != nil { i.loggerWrapper.GetLogger(instance.Id).LogError("[%s] Error updating instance: %s", instance.Id, err) return nil, "", "", err } // Verifica se a instância já está rodando isInstanceRunning := i.clientPointer[instance.Id] != nil // Sincroniza as configurações na instância em execução (se já estiver conectada) err = i.whatsmeowService.UpdateInstanceSettings(instance.Id) if err != nil { i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Instance not in runtime yet, will be updated when connected", instance.Id) isInstanceRunning = false } else { i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Instance settings updated successfully in runtime", instance.Id) isInstanceRunning = true } // Se a instância não estiver rodando, inicia uma nova if !isInstanceRunning { i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Starting new client instance", instance.Id) i.killChannel[instance.Id] = make(chan bool) clientData := &whatsmeow_service.ClientData{ Instance: instance, Subscriptions: subscribedEvents, Phone: data.Phone, IsProxy: false, } if instance.Proxy != "" || i.config.ProxyHost != "" { var proxyConfig ProxyConfig err := json.Unmarshal([]byte(instance.Proxy), &proxyConfig) if err != nil { i.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error unmarshalling proxy config: %v", instance.Id, err) return nil, "", "", err } if proxyConfig.Host != "" || i.config.ProxyHost != "" { clientData.IsProxy = true } } go i.whatsmeowService.StartClient(clientData) } else { i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Instance already running, settings updated without restarting client", instance.Id) } // logger.LogInfo("Waiting 1 seconds") // time.Sleep(1000 * time.Millisecond) // if i.clientPointer[instance.Id] != nil { // if !i.clientPointer[instance.Id].IsConnected() { // return instance, "", "", fmt.Errorf("failed to connect") // } // } else { // return instance, "", "", fmt.Errorf("failed to connect") // } return instance, instance.Jid, eventString, nil } func (i instances) Reconnect(instance *instance_model.Instance) error { _, err := i.ensureClientConnected(instance.Id) if err != nil { return err } return i.whatsmeowService.ReconnectClient(instance.Id) } func (i instances) Disconnect(instance *instance_model.Instance) (*instance_model.Instance, error) { client, err := i.ensureClientConnected(instance.Id) if err != nil { return instance, err } if client.IsConnected() { if client.IsLoggedIn() { i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Disconnection successful", instance.Id) i.killChannel[instance.Id] <- true instance.Events = "" err := i.instanceRepository.Update(instance) if err != nil { return instance, err } return instance, nil } } i.loggerWrapper.GetLogger(instance.Id).LogWarn("[%s] Ignoring disconnect as it was not connected", instance.Id) return instance, nil } func (i instances) Logout(instance *instance_model.Instance) (*instance_model.Instance, error) { client, err := i.ensureClientConnected(instance.Id) if err != nil { return instance, err } if client.IsLoggedIn() && client.IsConnected() { err := client.Logout(context.Background()) if err != nil { return instance, err } instance.Connected = false err = i.instanceRepository.Update(instance) if err != nil { return instance, err } select { case i.killChannel[instance.Id] <- true: case <-time.After(5 * time.Second): } delete(i.clientPointer, instance.Id) delete(i.killChannel, instance.Id) i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Logout successful", instance.Id) return instance, nil } if client.IsConnected() { client.Disconnect() select { case i.killChannel[instance.Id] <- true: case <-time.After(5 * time.Second): } delete(i.clientPointer, instance.Id) delete(i.killChannel, instance.Id) i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Disconnection successful", instance.Id) return instance, nil } i.loggerWrapper.GetLogger(instance.Id).LogWarn("[%s] Ignoring logout as it was not connected", instance.Id) return instance, fmt.Errorf("ignoring logout as it was not connected") } func (i instances) Status(instance *instance_model.Instance) (*StatusStruct, error) { client := i.clientPointer[instance.Id] if client == nil { return &StatusStruct{ Connected: false, LoggedIn: false, }, nil } isConnected := client.IsConnected() isLoggedIn := client.IsLoggedIn() var myJid *types.JID var name string if isLoggedIn { myJid = client.Store.ID name = client.Store.PushName } return &StatusStruct{ Connected: isConnected, LoggedIn: isLoggedIn, myJid: myJid, Name: name, }, nil } func (i instances) GetQr(instance *instance_model.Instance) (*QrcodeStruct, error) { logger := i.loggerWrapper.GetLogger(instance.Id) client := i.clientPointer[instance.Id] // Se não há cliente ou o cliente está logado, precisamos iniciar um novo cliente if client == nil || client.IsLoggedIn() { if client != nil && client.IsLoggedIn() { logger.LogInfo("[%s] Client is logged in, starting new instance for QR code", instance.Id) } else { logger.LogInfo("[%s] No client found, starting new instance for QR code", instance.Id) } // Iniciar nova instância para gerar QR code err := i.whatsmeowService.StartInstance(instance.Id) if err != nil { logger.LogError("[%s] Failed to start instance: %v", instance.Id, err) return nil, fmt.Errorf("failed to start instance: %w", err) } // Aguardar um pouco para o cliente iniciar e gerar QR code logger.LogInfo("[%s] Waiting for QR code generation...", instance.Id) time.Sleep(3 * time.Second) // Verificar novamente se há cliente client = i.clientPointer[instance.Id] if client != nil && client.IsLoggedIn() { return nil, fmt.Errorf("session already logged in") } } else if !client.IsConnected() { // Se o cliente existe mas não está conectado, pode estar aguardando QR code logger.LogInfo("[%s] Client exists but not connected, checking for existing QR code", instance.Id) } // Buscar instância atualizada do banco para pegar o QR code mais recente instance, err := i.instanceRepository.GetInstanceByID(instance.Id) if err != nil { return nil, err } // If a passkey ceremony is in progress, there is no QR to scan — return the // passkey stage + the #wapk openUrl so the manager can render the // "Abrir WhatsApp Web" button. Checked before the empty-QR branch because // during a passkey ceremony instance.Qrcode is empty. if store := i.whatsmeowService.PasskeyCeremonyStore(); store != nil { if token, state, ok := store.StateByInstance(instance.Id); ok { logger.LogInfo("[%s] Passkey ceremony active (stage=%s) — returning passkey info instead of QR", instance.Id, state.Stage) return &QrcodeStruct{ PasskeyStage: state.Stage, PasskeyCode: state.Code, PasskeyOpenURL: buildPasskeyOpenURL(token), }, nil } } code := instance.Qrcode if code == "" { // Se não há QR code ainda, aguardar um pouco mais e tentar novamente logger.LogInfo("[%s] No QR code available yet, waiting a bit more...", instance.Id) time.Sleep(2 * time.Second) instance, err = i.instanceRepository.GetInstanceByID(instance.Id) if err != nil { return nil, err } code = instance.Qrcode if code == "" { return nil, fmt.Errorf("no QR code available. Please wait a moment and try again") } } parts := strings.Split(code, "|") if len(parts) < 2 { return nil, fmt.Errorf("invalid QR code format") } qr := &QrcodeStruct{ Qrcode: parts[0], Code: parts[1], } return qr, nil } // buildPasskeyOpenURL builds the URL the manager opens to start the passkey // ceremony: https://web.whatsapp.com/#wapk=. // publicBase must be the PUBLICLY reachable API base the browser can hit; set it // via PASSKEY_PUBLIC_URL. Kept in sync with the event handler in whatsmeow.go. func buildPasskeyOpenURL(token string) string { publicBase := os.Getenv("PASSKEY_PUBLIC_URL") if publicBase == "" { publicBase = "" } payload := fmt.Sprintf(`{"t":%q,"b":%q}`, token, publicBase) wapk := base64.RawURLEncoding.EncodeToString([]byte(payload)) return "https://web.whatsapp.com/#wapk=" + wapk } func (i instances) Pair(data *PairStruct, instance *instance_model.Instance) (*PairReturnStruct, error) { logger := i.loggerWrapper.GetLogger(instance.Id) client := i.clientPointer[instance.Id] if client == nil || !client.IsConnected() { if client != nil && client.IsLoggedIn() { return nil, fmt.Errorf("instance is already authenticated") } logger.LogInfo("[%s] No active connection, starting instance for phone pairing", instance.Id) if err := i.whatsmeowService.StartInstance(instance.Id); err != nil { logger.LogError("[%s] Failed to start instance for pairing: %v", instance.Id, err) return nil, fmt.Errorf("failed to start instance: %w", err) } // Wait for the WA websocket connection and initial QR generation to establish. // PairPhone must be called after the QR event is received per whatsmeow docs. time.Sleep(3 * time.Second) client = i.clientPointer[instance.Id] if client == nil { return nil, fmt.Errorf("failed to initialize client for pairing") } } if client.IsLoggedIn() { return nil, fmt.Errorf("instance is already authenticated") } code, err := client.PairPhone(context.Background(), data.Phone, true, whatsmeow.PairClientChrome, "Chrome (Linux)") if err != nil { logger.LogError("[%s] PairPhone failed: %v", instance.Id, err) return nil, fmt.Errorf("pairing failed: %w", err) } return &PairReturnStruct{PairingCode: code}, nil } func (i instances) GetAll() ([]*instance_model.Instance, error) { instances, err := i.instanceRepository.GetAll(i.config.ClientName) if err != nil { return nil, err } for _, instance := range instances { if client := i.clientPointer[instance.Id]; client != nil { instance.Connected = client.IsLoggedIn() } else { instance.Connected = false } instance.Proxy = "" } return instances, nil } func (i instances) Info(instanceId string) (*instance_model.Instance, error) { instance, err := i.instanceRepository.GetInstanceByID(instanceId) if err != nil { return nil, err } // Atualiza o status connected com base no estado real do cliente if client := i.clientPointer[instance.Id]; client != nil { instance.Connected = client.IsLoggedIn() } else { instance.Connected = false } instance.Proxy = "" return instance, nil } func (i instances) Delete(id string) error { instance, err := i.instanceRepository.GetInstanceByID(id) if err != nil { return err } if i.clientPointer[instance.Id] != nil && i.clientPointer[instance.Id].IsConnected() { if i.clientPointer[instance.Id].IsLoggedIn() { i.clientPointer[instance.Id].Logout(context.Background()) } i.clientPointer[instance.Id].Disconnect() } // Limpar todos os recursos da instância antes de deletar delete(i.clientPointer, instance.Id) if i.killChannel[instance.Id] != nil { close(i.killChannel[instance.Id]) delete(i.killChannel, instance.Id) } // Limpar cache via whatsmeow service err = i.whatsmeowService.ClearInstanceCache(instance.Id, instance.Token) if err != nil { i.loggerWrapper.GetLogger(instance.Id).LogWarn("[%s] Failed to clear instance cache: %v", instance.Id, err) } err = i.instanceRepository.Delete(id) if err != nil { return err } return nil } func (i instances) SetProxy(id string, proxyConfig *ProxyConfig) error { instance, err := i.instanceRepository.GetInstanceByID(id) if err != nil { return err } // Validate proxy configuration if proxyConfig == nil { return fmt.Errorf("proxy configuration cannot be nil") } if proxyConfig.Host == "" { return fmt.Errorf("proxy host is required") } if proxyConfig.Port == "" { return fmt.Errorf("proxy port is required") } proxyConfig.Protocol = utils.NormalizeProxyProtocol(proxyConfig.Protocol, proxyConfig.Port) // Convert proxy config to JSON proxyJSON, err := json.Marshal(proxyConfig) if err != nil { i.loggerWrapper.GetLogger(id).LogError("[%s] Failed to marshal proxy config: %v", id, err) return fmt.Errorf("failed to marshal proxy configuration: %v", err) } instance.Proxy = string(proxyJSON) // Update instance in database err = i.instanceRepository.Update(instance) if err != nil { i.loggerWrapper.GetLogger(id).LogError("[%s] Failed to update instance with proxy: %v", id, err) return err } i.loggerWrapper.GetLogger(id).LogInfo("[%s] Proxy configuration updated: %s://%s:%s", id, proxyConfig.Protocol, proxyConfig.Host, proxyConfig.Port) // Reconnect to apply proxy changes go i.Reconnect(instance) return nil } func (i instances) SetProxyFromStruct(id string, data *SetProxyStruct) error { if data == nil { return fmt.Errorf("proxy data cannot be nil") } proxyConfig := &ProxyConfig{ Protocol: data.Protocol, Host: data.Host, Port: data.Port, Username: data.Username, Password: data.Password, } return i.SetProxy(id, proxyConfig) } func (i instances) RemoveProxy(id string) error { instance, err := i.instanceRepository.GetInstanceByID(id) if err != nil { return err } instance.Proxy = "" err = i.instanceRepository.Update(instance) if err != nil { return err } i.loggerWrapper.GetLogger(id).LogInfo("[%s] Proxy configuration removed", id) go i.Reconnect(instance) return nil } func (i instances) ForceReconnect(instanceId string, number string) error { if i.clientPointer[instanceId].IsConnected() && i.clientPointer[instanceId].IsLoggedIn() { return fmt.Errorf("client already connected") } err := i.whatsmeowService.ForceUpdateJid(instanceId, number) if err != nil { return err } instance, err := i.instanceRepository.GetInstanceByID(instanceId) if err != nil { return err } subscribedEvents := strings.Split(instance.Events, ",") i.killChannel[instance.Id] = make(chan bool) clientData := &whatsmeow_service.ClientData{ Instance: instance, Subscriptions: subscribedEvents, Phone: "", IsProxy: false, } if instance.Proxy != "" || i.config.ProxyHost != "" { var proxyConfig ProxyConfig err := json.Unmarshal([]byte(instance.Proxy), &proxyConfig) if err != nil { i.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error unmarshalling proxy config: %v", instance.Id, err) return err } if proxyConfig.Host != "" || i.config.ProxyHost != "" { clientData.IsProxy = true } } if i.clientPointer[instance.Id] != nil { client := i.clientPointer[instance.Id] client.Disconnect() select { case i.killChannel[instance.Id] <- true: case <-time.After(5 * time.Second): } delete(i.clientPointer, instance.Id) delete(i.killChannel, instance.Id) } go i.whatsmeowService.StartClient(clientData) time.Sleep(2 * time.Second) if i.clientPointer[instance.Id] != nil { if !i.clientPointer[instance.Id].IsConnected() { return fmt.Errorf("failed to connect") } if !i.clientPointer[instance.Id].IsLoggedIn() { return fmt.Errorf("failed to login") } } else { return fmt.Errorf("failed to connect") } return nil } func (i instances) GetInstanceByToken(token string) (*instance_model.Instance, error) { return i.instanceRepository.GetInstanceByToken(token) } func (i instances) GetLogs(instanceId string, startDate, endDate time.Time, level string, limit int) ([]logger_wrapper.LogEntry, error) { // Inicializa o slice vazio para garantir que nunca retorne null logs := make([]logger_wrapper.LogEntry, 0) // Define valores padrão if limit <= 0 { limit = 100 // Limite padrão de 100 registros } // Se não foi fornecida data inicial, usa 7 dias atrás if startDate.IsZero() { startDate = time.Now().AddDate(0, 0, -7) } // Se não foi fornecida data final, usa data atual if endDate.IsZero() { endDate = time.Now() } // Ajusta as datas para início e fim do dia startDate = time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, 0, 0, time.UTC) endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, 59, 999999999, time.UTC) // Garante que a data inicial não seja posterior à data final if startDate.After(endDate) { return logs, fmt.Errorf("data inicial não pode ser posterior à data final") } // Níveis de log válidos validLevels := map[string]bool{ "INFO": true, "ERROR": true, "WARN": true, "DEBUG": true, } var levelArray []string if level == "" { // Se nenhum nível foi especificado, usa todos levelArray = []string{"INFO", "ERROR", "WARN", "DEBUG"} } else { // Divide e normaliza os níveis fornecidos for _, l := range strings.Split(level, ",") { l = strings.TrimSpace(strings.ToUpper(l)) if !validLevels[l] { return logs, fmt.Errorf("nível de log inválido: %s", l) } levelArray = append(levelArray, l) } } // Lê os logs do arquivo logPath := filepath.Join(i.config.LogDirectory, instanceId, "instance.log") file, err := os.Open(logPath) if err != nil { if os.IsNotExist(err) { return logs, nil // Retorna array vazio se arquivo não existir } return logs, fmt.Errorf("erro ao abrir arquivo de log: %v", err) } defer file.Close() scanner := bufio.NewScanner(file) // Aumenta o buffer do scanner para lidar com linhas grandes const maxCapacity = 1024 * 1024 // 1MB buf := make([]byte, maxCapacity) scanner.Buffer(buf, maxCapacity) for scanner.Scan() { var entry logger_wrapper.LogEntry if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil { continue // Ignora linhas inválidas } // Ajusta o timestamp da entrada para UTC para comparação correta entry.Timestamp = entry.Timestamp.UTC() // Aplica os filtros if entry.Timestamp.Before(startDate) || entry.Timestamp.After(endDate) { continue } if !slices.Contains(levelArray, entry.Level) { continue } logs = append(logs, entry) // Verifica o limite if len(logs) >= limit { break } } if err := scanner.Err(); err != nil { return logs, fmt.Errorf("erro ao ler arquivo de log: %v", err) } // Ordena os logs por timestamp em ordem decrescente sort.Slice(logs, func(i, j int) bool { return logs[i].Timestamp.After(logs[j].Timestamp) }) return logs, nil } func (i instances) GetAdvancedSettings(instanceId string) (*instance_model.AdvancedSettings, error) { i.loggerWrapper.GetLogger(instanceId).LogInfo("[%s] Getting advanced settings", instanceId) settings, err := i.instanceRepository.GetAdvancedSettings(instanceId) if err != nil { i.loggerWrapper.GetLogger(instanceId).LogError("[%s] Error getting advanced settings: %v", instanceId, err) return nil, err } return settings, nil } func (i instances) UpdateAdvancedSettings(instanceId string, settings *instance_model.AdvancedSettings) error { i.loggerWrapper.GetLogger(instanceId).LogInfo("[%s] Updating advanced settings", instanceId) err := i.instanceRepository.UpdateAdvancedSettings(instanceId, settings) if err != nil { i.loggerWrapper.GetLogger(instanceId).LogError("[%s] Error updating advanced settings: %v", instanceId, err) return err } // Sincroniza as configurações na instância em execução err = i.whatsmeowService.UpdateInstanceAdvancedSettings(instanceId) if err != nil { i.loggerWrapper.GetLogger(instanceId).LogWarn("[%s] Error syncing advanced settings to runtime: %v", instanceId, err) // Não falha a operação, apenas loga o warning } i.loggerWrapper.GetLogger(instanceId).LogInfo("[%s] Advanced settings updated successfully", instanceId) return nil } func NewInstanceService( instanceRepository instance_repository.InstanceRepository, killChannel map[string](chan bool), clientPointer map[string]*whatsmeow.Client, whatsmeowService whatsmeow_service.WhatsmeowService, config *config.Config, loggerWrapper *logger_wrapper.LoggerManager, ) InstanceService { return &instances{ instanceRepository: instanceRepository, killChannel: killChannel, clientPointer: clientPointer, whatsmeowService: whatsmeowService, config: config, loggerWrapper: loggerWrapper, } }