diff --git a/.gitignore b/.gitignore index 03ad15e..321e300 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,10 @@ coverage.* .idea/ .vscode/ .DS_Store + +# ImobDeal — arquivos locais pra rodar o Evolution Go no PC +docker-compose.yml +init-db.sql +deploy_ssh.py +check_deploy.py +*.local.py diff --git a/pkg/instance/handler/instance_handler.go b/pkg/instance/handler/instance_handler.go index ebf8406..fe4a7ed 100644 --- a/pkg/instance/handler/instance_handler.go +++ b/pkg/instance/handler/instance_handler.go @@ -16,6 +16,7 @@ type InstanceHandler interface { Connect(ctx *gin.Context) Reconnect(ctx *gin.Context) Disconnect(ctx *gin.Context) + SetPresence(ctx *gin.Context) Logout(ctx *gin.Context) Delete(ctx *gin.Context) Status(ctx *gin.Context) @@ -205,6 +206,48 @@ func (i *instanceHandler) Disconnect(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"message": "success"}) } +// IMOBDEAL PATCH: SetPresence endpoint +// @Summary Set instance presence (available/unavailable) +// @Description Marks the instance as available or unavailable WITHOUT disconnecting. +// @Description Mimics WhatsApp Web's behavior when minimized — the phone resumes +// @Description receiving push notifications while the linked device stays connected. +// @Tags Instance +// @Accept json +// @Produce json +// @Param body body instance_service.SetPresenceStruct true "State: available or unavailable" +// @Success 200 {object} gin.H "Presence updated" +// @Failure 400 {object} gin.H "Invalid state" +// @Failure 500 {object} gin.H "Internal server error" +// @Router /instance/presence [post] +func (i *instanceHandler) SetPresence(ctx *gin.Context) { + getInstance := ctx.MustGet("instance") + + instance, ok := getInstance.(*instance_model.Instance) + if !ok { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "instance not found"}) + return + } + + var data instance_service.SetPresenceStruct + if err := ctx.ShouldBindJSON(&data); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Validate input here so misuse returns 4xx and service errors remain 5xx + if data.State != "available" && data.State != "unavailable" { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid state, must be 'available' or 'unavailable'"}) + return + } + + if err := i.instanceService.SetPresence(ctx.Request.Context(), &data, instance); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + ctx.JSON(http.StatusOK, gin.H{"message": "success", "state": data.State}) +} + // Logout from instance // @Summary Logout from instance // @Description Logout from instance diff --git a/pkg/instance/service/instance_service.go b/pkg/instance/service/instance_service.go index 32d831e..28ec7d4 100644 --- a/pkg/instance/service/instance_service.go +++ b/pkg/instance/service/instance_service.go @@ -28,6 +28,7 @@ type InstanceService interface { 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) + SetPresence(ctx context.Context, data *SetPresenceStruct, 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) @@ -79,6 +80,12 @@ type ConnectStruct struct { NatsEnable string `json:"natsEnable"` } +// IMOBDEAL PATCH: struct para a rota POST /instance/presence +// state deve ser "available" ou "unavailable" +type SetPresenceStruct struct { + State string `json:"state" binding:"required"` +} + type StatusStruct struct { Connected bool LoggedIn bool @@ -319,6 +326,42 @@ func (i instances) Disconnect(instance *instance_model.Instance) (*instance_mode return instance, nil } +// IMOBDEAL PATCH: SetPresence expõe SendPresence do whatsmeow como endpoint HTTP. +// Permite marcar o cliente como "available" (ativo, intercepta push do celular) ou +// "unavailable" (standby, celular volta a receber push) sem desconectar a sessão. +// Idêntico ao comportamento do WhatsApp Web quando minimizado/em background. +// A validação do campo State acontece no handler HTTP (retorna 400 em caso de valor inválido); +// qualquer erro que chega aqui é considerado 5xx. +func (i instances) SetPresence(ctx context.Context, data *SetPresenceStruct, instance *instance_model.Instance) error { + client, err := i.ensureClientConnected(instance.Id) + if err != nil { + return err + } + + if !client.IsConnected() || !client.IsLoggedIn() { + return errors.New("client is not connected or not logged in") + } + + var state types.Presence + switch data.State { + case "available": + state = types.PresenceAvailable + case "unavailable": + state = types.PresenceUnavailable + default: + // Should never reach here because handler validates first, but keep as safety net. + return errors.New("invalid state") + } + + if err := client.SendPresence(ctx, state); err != nil { + i.loggerWrapper.GetLogger(instance.Id).LogError("[%s] SetPresence failed: %v", instance.Id, err) + return err + } + + i.loggerWrapper.GetLogger(instance.Id).LogInfo("[%s] Presence set to %s", instance.Id, data.State) + return nil +} + func (i instances) Logout(instance *instance_model.Instance) (*instance_model.Instance, error) { client, err := i.ensureClientConnected(instance.Id) if err != nil { diff --git a/pkg/message/service/message_service.go b/pkg/message/service/message_service.go index 36ffb68..89c7406 100644 --- a/pkg/message/service/message_service.go +++ b/pkg/message/service/message_service.go @@ -406,6 +406,9 @@ func (m *messageService) EditMessage(data *EditMessageStruct, instance *instance return "", "", errors.New("invalid phone number") } + // IMOBDEAL PATCH: usar ExtendedTextMessage em vez de Conversation. + // SendText envia como ExtendedTextMessage; o WhatsApp ignora silenciosamente + // edições quando o tipo difere do original. resp, err := client.SendMessage( context.Background(), recipient, @@ -413,10 +416,12 @@ func (m *messageService) EditMessage(data *EditMessageStruct, instance *instance recipient, data.MessageID, &waE2E.Message{ - Conversation: proto.String(data.Message), + ExtendedTextMessage: &waE2E.ExtendedTextMessage{ + Text: &data.Message, + }, })) if err != nil { - m.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error revoking message: %v", instance.Id, err) + m.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error editing message: %v", instance.Id, err) return "", "", err } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 0064014..5e6483f 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -102,6 +102,7 @@ func (r *Routes) AssignRoutes(eng *gin.Engine) { routes.POST("/pair", r.jidValidationMiddleware.ValidateNumberField(), r.instanceHandler.Pair) routes.POST("/disconnect", r.instanceHandler.Disconnect) routes.POST("/reconnect", r.instanceHandler.Reconnect) + routes.POST("/presence", r.instanceHandler.SetPresence) // IMOBDEAL PATCH routes.DELETE("/logout", r.instanceHandler.Logout) routes.GET("/:instanceId/advanced-settings", r.instanceHandler.GetAdvancedSettings) routes.PUT("/:instanceId/advanced-settings", r.instanceHandler.UpdateAdvancedSettings)