Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 157 additions & 41 deletions pkg/whatsmeow/service/whatsmeow.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,34 +92,38 @@ type whatsmeowService struct {
}

type MyClient struct {
service WhatsmeowService
WAClient *whatsmeow.Client
eventHandlerID uint32
userID string
Instance *instance_model.Instance
token string
subscriptions []string
webhookUrl string
rabbitmqEnable string
natsEnable string
websocketEnable string
instanceRepository instance_repository.InstanceRepository
messageRepository message_repository.MessageRepository
labelRepository label_repository.LabelRepository
pollService poll_service.PollService // NOVO: Serviço de enquetes
clientPointer map[string]*whatsmeow.Client
killChannel map[string](chan bool)
userInfoCache *cache.Cache
config *config.Config
historySyncID int32
rabbitmqProducer producer_interfaces.Producer
webhookProducer producer_interfaces.Producer
websocketProducer producer_interfaces.Producer
mediaStorage storage_interfaces.MediaStorage
processedMessages *cache.Cache
natsProducer producer_interfaces.Producer
loggerWrapper *logger_wrapper.LoggerManager
qrcodeCount int
service WhatsmeowService
WAClient *whatsmeow.Client
eventHandlerID uint32
userID string
Instance *instance_model.Instance
token string
subscriptions []string
webhookUrl string
rabbitmqEnable string
natsEnable string
websocketEnable string
instanceRepository instance_repository.InstanceRepository
messageRepository message_repository.MessageRepository
labelRepository label_repository.LabelRepository
pollService poll_service.PollService // NOVO: Serviço de enquetes
clientPointer map[string]*whatsmeow.Client
killChannel map[string](chan bool)
userInfoCache *cache.Cache
config *config.Config
historySyncID int32
rabbitmqProducer producer_interfaces.Producer
webhookProducer producer_interfaces.Producer
websocketProducer producer_interfaces.Producer
mediaStorage storage_interfaces.MediaStorage
processedMessages *cache.Cache
natsProducer producer_interfaces.Producer
loggerWrapper *logger_wrapper.LoggerManager
qrcodeCount int
presenceMu sync.Mutex
presenceStop chan struct{}
presenceOnline bool
presenceGen uint64
}

type ClientData struct {
Expand Down Expand Up @@ -171,8 +175,11 @@ func (w whatsmeowService) ReconnectClient(instanceId string) error {
}
}

if mycli, ok := w.myClientPointer[instanceId]; ok {
mycli.stopPresence()
}

// Passo 2: Limpar todos os recursos da instância
w.loggerWrapper.GetLogger(instanceId).LogInfo("[%s] Cleaning up resources", instanceId)

// Enviar sinal de kill se o canal existir
if killChan, exists := w.killChannel[instanceId]; exists {
Expand Down Expand Up @@ -793,12 +800,14 @@ func (w whatsmeowService) StartClient(cd *ClientData) {
}
}

func schedulePresenceUpdates(mycli *MyClient) {
func schedulePresenceUpdates(mycli *MyClient, stop <-chan struct{}) {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()

for {
select {
case <-stop:
return
case <-ticker.C:
// Verificar se a instância ainda existe
_, err := mycli.instanceRepository.GetInstanceByID(mycli.userID)
Expand All @@ -813,9 +822,7 @@ func schedulePresenceUpdates(mycli *MyClient) {
randomInterval := time.Duration(1+rand.Intn(3)) * time.Hour
ticker = time.NewTicker(randomInterval)

case <-mycli.killChannel[mycli.userID]:
mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Received kill signal, stopping presence updates", mycli.userID)
return // Encerra a goroutine quando receber sinal de kill

}
}
}
Expand Down Expand Up @@ -844,6 +851,120 @@ func processPresenceUpdates(mycli *MyClient) {
}
}

// pulseCompanionActivity records that the linked device is in use, then
// returns to unavailable. WhatsApp drops an inactive companion after 30 days.
// Leaving it available suppresses phone push notifications, so the available
// state is only a short blip. This goroutine must not read killChannel.
func pulseCompanionActivity(mycli *MyClient, gen uint64) {
if mycli == nil || mycli.WAClient == nil || !mycli.presenceCurrent(gen) {
return
}
logger := mycli.loggerWrapper.GetLogger(mycli.userID)
if err := mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable); err != nil {
logger.LogWarn("[%s] Failed to send activity presence: %v", mycli.userID, err)
return
}
time.Sleep(time.Duration(3+rand.Intn(5)) * time.Second)
if err := restoreUnavailable(mycli, gen); err != nil {
logger.LogWarn("[%s] Failed to restore unavailable presence: %v", mycli.userID, err)
}
}

func restoreUnavailable(mycli *MyClient, gen uint64) error {
var err error
for attempt := 0; attempt < 3; attempt++ {
if !mycli.presenceCurrent(gen) || mycli.WAClient == nil || !mycli.WAClient.IsLoggedIn() {
return nil
}
if mycli.Instance != nil && mycli.Instance.AlwaysOnline {
return nil
}
err = mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable)
if err == nil {
mycli.loggerWrapper.GetLogger(mycli.userID).LogInfo("[%s] Companion activity pulsed; presence restored to unavailable", mycli.userID)
return nil
}
time.Sleep(time.Second)
}
return err
}

func scheduleCompanionActivityPulse(mycli *MyClient, stop <-chan struct{}, gen uint64) {
timer := time.NewTimer(companionActivityInterval())
defer timer.Stop()
for {
select {
case <-stop:
return
case <-timer.C:
if !mycli.presenceCurrent(gen) || mycli.WAClient == nil || !mycli.WAClient.IsLoggedIn() {
return
}
if mycli.Instance != nil && mycli.Instance.AlwaysOnline {
return
}
pulseCompanionActivity(mycli, gen)
timer.Reset(companionActivityInterval())
}
}
}

func companionActivityInterval() time.Duration {
return 20*time.Hour + time.Duration(rand.Intn(4*60))*time.Minute
}

func (mycli *MyClient) presenceCurrent(gen uint64) bool {
mycli.presenceMu.Lock()
defer mycli.presenceMu.Unlock()
return mycli.presenceGen == gen
}

func (mycli *MyClient) reconcilePresence() {
if mycli == nil || mycli.WAClient == nil || !mycli.WAClient.IsLoggedIn() {
return
}
online := mycli.Instance != nil && mycli.Instance.AlwaysOnline

mycli.presenceMu.Lock()
if mycli.presenceStop != nil && mycli.presenceOnline == online {
mycli.presenceMu.Unlock()
return
}
if mycli.presenceStop != nil {
close(mycli.presenceStop)
}
stop := make(chan struct{})
mycli.presenceStop = stop
mycli.presenceOnline = online
mycli.presenceGen++
gen := mycli.presenceGen
mycli.presenceMu.Unlock()

if online {
go schedulePresenceUpdates(mycli, stop)
if err := mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable); err != nil {
mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err)
}
return
}
go scheduleCompanionActivityPulse(mycli, stop, gen)
go pulseCompanionActivity(mycli, gen)
}

func (mycli *MyClient) stopPresence() {
if mycli == nil {
return
}
mycli.presenceMu.Lock()
defer mycli.presenceMu.Unlock()
if mycli.presenceStop != nil {
close(mycli.presenceStop)
mycli.presenceStop = nil
}
}



func (mycli *MyClient) myEventHandler(rawEvt interface{}) {
userID := mycli.userID
postMap := make(map[string]interface{})
Expand Down Expand Up @@ -905,14 +1026,8 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) {

postMap["data"] = dataMap

go schedulePresenceUpdates(mycli)

err := mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable)
if err != nil {
mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err)
} else {
mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Marked self as available", mycli.userID)
}
var err error
mycli.reconcilePresence()

mycli.Instance.Connected = true
mycli.Instance.DisconnectReason = ""
Expand Down Expand Up @@ -2606,6 +2721,7 @@ func (w whatsmeowService) UpdateInstanceAdvancedSettings(instanceId string) erro

// Atualiza a instância no MyClient com as advanced settings atualizadas
myClient.Instance = instance
myClient.reconcilePresence()

w.loggerWrapper.GetLogger(instanceId).LogInfo("[%s] Advanced settings updated in runtime successfully", instanceId)
return nil
Expand Down