Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions src/domains/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type IAppUsecase interface {
type DevicesResponse struct {
Name string `json:"name"`
Device string `json:"device"`
JID string `json:"jid,omitempty"`
}

type LoginResponse struct {
Expand Down
13 changes: 8 additions & 5 deletions src/infrastructure/whatsapp/event_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// handleCallOffer handles incoming call events and optionally auto-rejects them
func handleCallOffer(ctx context.Context, evt *events.CallOffer, chatStorageRepo domainChatStorage.IChatStorageRepository, deviceID string, client *whatsmeow.Client) {
func handleCallOffer(ctx context.Context, evt *events.CallOffer, chatStorageRepo domainChatStorage.IChatStorageRepository, sessionID string, deviceID string, client *whatsmeow.Client) {
logrus.Infof("Incoming call from %s (CallID: %s)", evt.CallCreator.String(), evt.CallID)

// Auto-reject call if configured
Expand Down Expand Up @@ -47,15 +47,15 @@ func handleCallOffer(ctx context.Context, evt *events.CallOffer, chatStorageRepo
go func(e *events.CallOffer, c *whatsmeow.Client, rejected bool) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardCallOfferToWebhook(webhookCtx, e, deviceID, c, rejected); err != nil {
if err := forwardCallOfferToWebhook(webhookCtx, e, sessionID, deviceID, c, rejected); err != nil {
logrus.Errorf("Failed to forward call event to webhook: %v", err)
}
}(evt, client, autoRejected)
}
}

// createCallOfferPayload creates a webhook payload for incoming call events
func createCallOfferPayload(ctx context.Context, evt *events.CallOffer, deviceID string, client *whatsmeow.Client, autoRejected bool) map[string]any {
func createCallOfferPayload(ctx context.Context, evt *events.CallOffer, sessionID string, deviceID string, client *whatsmeow.Client, autoRejected bool) map[string]any {
body := make(map[string]any)
payload := make(map[string]any)

Expand Down Expand Up @@ -83,13 +83,16 @@ func createCallOfferPayload(ctx context.Context, evt *events.CallOffer, deviceID
if deviceID != "" {
body["device_id"] = deviceID
}
if sessionID != "" {
body["session_id"] = sessionID
}
body["payload"] = payload

return body
}

// forwardCallOfferToWebhook forwards incoming call events to the configured webhook URLs
func forwardCallOfferToWebhook(ctx context.Context, evt *events.CallOffer, deviceID string, client *whatsmeow.Client, autoRejected bool) error {
payload := createCallOfferPayload(ctx, evt, deviceID, client, autoRejected)
func forwardCallOfferToWebhook(ctx context.Context, evt *events.CallOffer, sessionID string, deviceID string, client *whatsmeow.Client, autoRejected bool) error {
payload := createCallOfferPayload(ctx, evt, sessionID, deviceID, client, autoRejected)
return forwardPayloadToConfiguredWebhooks(ctx, payload, "call.offer")
}
9 changes: 6 additions & 3 deletions src/infrastructure/whatsapp/event_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
)

// forwardDeleteToWebhook sends a delete event to webhook
func forwardDeleteToWebhook(ctx context.Context, evt *events.DeleteForMe, message *domainChatStorage.Message, deviceID string, client *whatsmeow.Client) error {
payload, err := createDeletePayload(ctx, evt, message, deviceID, client)
func forwardDeleteToWebhook(ctx context.Context, evt *events.DeleteForMe, message *domainChatStorage.Message, sessionID string, deviceID string, client *whatsmeow.Client) error {
payload, err := createDeletePayload(ctx, evt, message, sessionID, deviceID, client)
if err != nil {
return err
}
Expand All @@ -20,7 +20,7 @@ func forwardDeleteToWebhook(ctx context.Context, evt *events.DeleteForMe, messag
}

// createDeletePayload creates a webhook payload for delete events
func createDeletePayload(ctx context.Context, evt *events.DeleteForMe, message *domainChatStorage.Message, deviceID string, client *whatsmeow.Client) (map[string]any, error) {
func createDeletePayload(ctx context.Context, evt *events.DeleteForMe, message *domainChatStorage.Message, sessionID string, deviceID string, client *whatsmeow.Client) (map[string]any, error) {
body := make(map[string]any)
payload := make(map[string]any)

Expand Down Expand Up @@ -49,6 +49,9 @@ func createDeletePayload(ctx context.Context, evt *events.DeleteForMe, message *
if deviceID != "" {
body["device_id"] = deviceID
}
if sessionID != "" {
body["session_id"] = sessionID
}
body["payload"] = payload

return body, nil
Expand Down
18 changes: 12 additions & 6 deletions src/infrastructure/whatsapp/event_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// createGroupInfoPayload creates a webhook payload for group information events
func createGroupInfoPayload(ctx context.Context, evt *events.GroupInfo, actionType string, jids []types.JID, deviceID string, client *whatsmeow.Client) map[string]any {
func createGroupInfoPayload(ctx context.Context, evt *events.GroupInfo, actionType string, jids []types.JID, sessionID string, deviceID string, client *whatsmeow.Client) map[string]any {
body := make(map[string]any)

// Create payload structure matching the expected format
Expand All @@ -35,6 +35,9 @@ func createGroupInfoPayload(ctx context.Context, evt *events.GroupInfo, actionTy
if deviceID != "" {
body["device_id"] = deviceID
}
if sessionID != "" {
body["session_id"] = sessionID
}

return body
}
Expand All @@ -55,7 +58,7 @@ func jidsToStrings(ctx context.Context, jids []types.JID, client *whatsmeow.Clie
}

// forwardGroupInfoToWebhook forwards group information events to the configured webhook URLs
func forwardGroupInfoToWebhook(ctx context.Context, evt *events.GroupInfo, deviceID string, client *whatsmeow.Client) error {
func forwardGroupInfoToWebhook(ctx context.Context, evt *events.GroupInfo, sessionID string, deviceID string, client *whatsmeow.Client) error {
// Send separate webhook events for each action type
actions := []struct {
actionType string
Expand All @@ -69,7 +72,7 @@ func forwardGroupInfoToWebhook(ctx context.Context, evt *events.GroupInfo, devic

for _, action := range actions {
if len(action.jids) > 0 {
payload := createGroupInfoPayload(ctx, evt, action.actionType, action.jids, deviceID, client)
payload := createGroupInfoPayload(ctx, evt, action.actionType, action.jids, sessionID, deviceID, client)

if err := forwardPayloadToConfiguredWebhooks(ctx, payload, "group.participants"); err != nil {
logrus.Warnf("Failed to forward group %s event to webhook: %v", action.actionType, err)
Expand All @@ -81,22 +84,22 @@ func forwardGroupInfoToWebhook(ctx context.Context, evt *events.GroupInfo, devic
}

// handleJoinedGroup handles the event when the connected device is added to a new group
func handleJoinedGroup(ctx context.Context, evt *events.JoinedGroup, deviceID string, client *whatsmeow.Client) {
func handleJoinedGroup(ctx context.Context, evt *events.JoinedGroup, sessionID string, deviceID string, client *whatsmeow.Client) {
log.Infof("Joined group %s (reason: %s, type: %s)", evt.JID, evt.Reason, evt.Type)

if len(config.WhatsappWebhook) > 0 {
go func(e *events.JoinedGroup, c *whatsmeow.Client) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardJoinedGroupToWebhook(webhookCtx, e, deviceID, c); err != nil {
if err := forwardJoinedGroupToWebhook(webhookCtx, e, sessionID, deviceID, c); err != nil {
logrus.Errorf("Failed to forward joined group event to webhook: %v", err)
}
}(evt, client)
}
}

// forwardJoinedGroupToWebhook forwards the JoinedGroup event to configured webhooks
func forwardJoinedGroupToWebhook(ctx context.Context, evt *events.JoinedGroup, deviceID string, client *whatsmeow.Client) error {
func forwardJoinedGroupToWebhook(ctx context.Context, evt *events.JoinedGroup, sessionID string, deviceID string, client *whatsmeow.Client) error {
// Get own JID to include in the payload
ownJID := client.Store.ID
if ownJID == nil {
Expand All @@ -123,6 +126,9 @@ func forwardJoinedGroupToWebhook(ctx context.Context, evt *events.JoinedGroup, d
if deviceID != "" {
body["device_id"] = deviceID
}
if sessionID != "" {
body["session_id"] = sessionID
}

return forwardPayloadToConfiguredWebhooks(ctx, body, "group.joined")
}
35 changes: 19 additions & 16 deletions src/infrastructure/whatsapp/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ func handler(ctx context.Context, instance *DeviceInstance, rawEvt any) {
chatStorageRepo := instance.GetChatStorage()
client := instance.GetClient()

sessionID := instance.ID()
deviceJID := instance.JID()

switch evt := rawEvt.(type) {
case *events.DeleteForMe:
handleDeleteForMe(ctx, evt, chatStorageRepo, instance.JID(), client)
handleDeleteForMe(ctx, evt, chatStorageRepo, sessionID, deviceJID, client)
case *events.AppStateSyncComplete:
handleAppStateSyncComplete(ctx, client, evt)
case *events.PairSuccess:
Expand All @@ -44,9 +47,9 @@ func handler(ctx context.Context, instance *DeviceInstance, rawEvt any) {
case *events.StreamReplaced:
handleStreamReplaced(ctx)
case *events.Message:
handleMessage(ctx, evt, chatStorageRepo, client)
handleMessage(ctx, evt, chatStorageRepo, sessionID, client)
case *events.Receipt:
handleReceipt(ctx, evt, instance.JID(), client)
handleReceipt(ctx, evt, sessionID, deviceJID, client)
case *events.Archive:
handleArchive(ctx, evt, chatStorageRepo, client)
case *events.Presence:
Expand All @@ -58,25 +61,25 @@ func handler(ctx context.Context, instance *DeviceInstance, rawEvt any) {
case *events.AppState:
handleAppState(ctx, evt)
case *events.GroupInfo:
handleGroupInfo(ctx, evt, instance.JID(), client)
handleGroupInfo(ctx, evt, sessionID, deviceJID, client)
case *events.JoinedGroup:
handleJoinedGroup(ctx, evt, instance.JID(), client)
handleJoinedGroup(ctx, evt, sessionID, deviceJID, client)
case *events.NewsletterJoin:
handleNewsletterJoin(ctx, evt, instance.JID(), client)
handleNewsletterJoin(ctx, evt, sessionID, deviceJID, client)
case *events.NewsletterLeave:
handleNewsletterLeave(ctx, evt, instance.JID(), client)
handleNewsletterLeave(ctx, evt, sessionID, deviceJID, client)
case *events.NewsletterLiveUpdate:
handleNewsletterLiveUpdate(ctx, evt, instance.JID(), client)
handleNewsletterLiveUpdate(ctx, evt, sessionID, deviceJID, client)
case *events.NewsletterMuteChange:
handleNewsletterMuteChange(ctx, evt, instance.JID(), client)
handleNewsletterMuteChange(ctx, evt, sessionID, deviceJID, client)
case *events.CallOffer:
handleCallOffer(ctx, evt, chatStorageRepo, instance.JID(), client)
handleCallOffer(ctx, evt, chatStorageRepo, sessionID, deviceJID, client)
}

instance.UpdateStateFromClient()
}

func handleDeleteForMe(ctx context.Context, evt *events.DeleteForMe, chatStorageRepo domainChatStorage.IChatStorageRepository, deviceID string, client *whatsmeow.Client) {
func handleDeleteForMe(ctx context.Context, evt *events.DeleteForMe, chatStorageRepo domainChatStorage.IChatStorageRepository, sessionID string, deviceID string, client *whatsmeow.Client) {
log.Infof("Deleted message %s for %s", evt.MessageID, evt.SenderJID.String())

// Find the message to get its chat JID
Expand All @@ -103,7 +106,7 @@ func handleDeleteForMe(ctx context.Context, evt *events.DeleteForMe, chatStorage
go func(c *whatsmeow.Client) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardDeleteToWebhook(webhookCtx, evt, message, deviceID, c); err != nil {
if err := forwardDeleteToWebhook(webhookCtx, evt, message, sessionID, deviceID, c); err != nil {
log.Errorf("Failed to forward delete event to webhook: %v", err)
}
}(client)
Expand Down Expand Up @@ -214,7 +217,7 @@ func handleStreamReplaced(_ context.Context) {
os.Exit(0)
}

func handleReceipt(ctx context.Context, evt *events.Receipt, deviceID string, client *whatsmeow.Client) {
func handleReceipt(ctx context.Context, evt *events.Receipt, sessionID string, deviceID string, client *whatsmeow.Client) {
sendReceipt := false
switch evt.Type {
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
Expand All @@ -231,7 +234,7 @@ func handleReceipt(ctx context.Context, evt *events.Receipt, deviceID string, cl
go func(e *events.Receipt, c *whatsmeow.Client) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardReceiptToWebhook(webhookCtx, e, deviceID, c); err != nil {
if err := forwardReceiptToWebhook(webhookCtx, e, sessionID, deviceID, c); err != nil {
logrus.Errorf("Failed to forward ack event to webhook: %v", err)
}
}(evt, client)
Expand All @@ -254,7 +257,7 @@ func handleAppState(_ context.Context, evt *events.AppState) {
log.Debugf("App state event: %+v / %+v", evt.Index, evt.SyncActionValue)
}

func handleGroupInfo(ctx context.Context, evt *events.GroupInfo, deviceID string, client *whatsmeow.Client) {
func handleGroupInfo(ctx context.Context, evt *events.GroupInfo, sessionID string, deviceID string, client *whatsmeow.Client) {
// Only process events that have actual changes
hasChanges := len(evt.Join) > 0 || len(evt.Leave) > 0 || len(evt.Promote) > 0 || len(evt.Demote) > 0 ||
evt.Name != nil || evt.Topic != nil || evt.Locked != nil || evt.Announce != nil
Expand Down Expand Up @@ -282,7 +285,7 @@ func handleGroupInfo(ctx context.Context, evt *events.GroupInfo, deviceID string
go func(e *events.GroupInfo, c *whatsmeow.Client) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardGroupInfoToWebhook(webhookCtx, e, deviceID, c); err != nil {
if err := forwardGroupInfoToWebhook(webhookCtx, e, sessionID, deviceID, c); err != nil {
logrus.Errorf("Failed to forward group info event to webhook: %v", err)
}
}(evt, client)
Expand Down
21 changes: 13 additions & 8 deletions src/infrastructure/whatsapp/event_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ const (

// WebhookEvent is the top-level structure for webhook payloads
type WebhookEvent struct {
Event string `json:"event"`
DeviceID string `json:"device_id"`
Payload map[string]any `json:"payload"`
Event string `json:"event"`
DeviceID string `json:"device_id"`
SessionID string `json:"session_id,omitempty"`
Payload map[string]any `json:"payload"`
}

// forwardMessageToWebhook is a helper function to forward message event to webhook url
func forwardMessageToWebhook(ctx context.Context, client *whatsmeow.Client, evt *events.Message) error {
webhookEvent, err := createWebhookEvent(ctx, client, evt)
func forwardMessageToWebhook(ctx context.Context, client *whatsmeow.Client, evt *events.Message, sessionID string) error {
webhookEvent, err := createWebhookEvent(ctx, client, evt, sessionID)
if err != nil {
return err
}
Expand All @@ -47,14 +48,18 @@ func forwardMessageToWebhook(ctx context.Context, client *whatsmeow.Client, evt
"device_id": webhookEvent.DeviceID,
"payload": webhookEvent.Payload,
}
if webhookEvent.SessionID != "" {
payload["session_id"] = webhookEvent.SessionID
}

return forwardPayloadToConfiguredWebhooks(ctx, payload, webhookEvent.Event)
}

func createWebhookEvent(ctx context.Context, client *whatsmeow.Client, evt *events.Message) (*WebhookEvent, error) {
func createWebhookEvent(ctx context.Context, client *whatsmeow.Client, evt *events.Message, sessionID string) (*WebhookEvent, error) {
webhookEvent := &WebhookEvent{
Event: EventTypeMessage,
Payload: make(map[string]any),
Event: EventTypeMessage,
SessionID: sessionID,
Payload: make(map[string]any),
}

// Set device_id
Expand Down
12 changes: 6 additions & 6 deletions src/infrastructure/whatsapp/event_message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"go.mau.fi/whatsmeow/types/events"
)

func handleMessage(ctx context.Context, evt *events.Message, chatStorageRepo domainChatStorage.IChatStorageRepository, client *whatsmeow.Client) {
func handleMessage(ctx context.Context, evt *events.Message, chatStorageRepo domainChatStorage.IChatStorageRepository, sessionID string, client *whatsmeow.Client) {
// Log message metadata
metaParts := buildMessageMetaParts(evt)
log.Infof("Received message %s from %s (%s): %+v",
Expand All @@ -40,7 +40,7 @@ func handleMessage(ctx context.Context, evt *events.Message, chatStorageRepo dom
handleAutoReply(ctx, evt, chatStorageRepo, client)

// Forward to webhook if configured
handleWebhookForward(ctx, evt, client)
handleWebhookForward(ctx, evt, sessionID, client)
}

func buildMessageMetaParts(evt *events.Message) []string {
Expand Down Expand Up @@ -99,7 +99,7 @@ func handleAutoMarkRead(ctx context.Context, evt *events.Message, client *whatsm
}
}

func handleWebhookForward(ctx context.Context, evt *events.Message, client *whatsmeow.Client) {
func handleWebhookForward(ctx context.Context, evt *events.Message, sessionID string, client *whatsmeow.Client) {
// Skip webhook for protocol messages that are internal sync messages
if protocolMessage := evt.Message.GetProtocolMessage(); protocolMessage != nil {
protocolType := protocolMessage.GetType().String()
Expand All @@ -116,12 +116,12 @@ func handleWebhookForward(ctx context.Context, evt *events.Message, client *what

if (len(config.WhatsappWebhook) > 0 || config.ChatwootEnabled) &&
!strings.Contains(evt.Info.SourceString(), "broadcast") {
go func(e *events.Message, c *whatsmeow.Client) {
go func(e *events.Message, c *whatsmeow.Client, sid string) {
webhookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := forwardMessageToWebhook(webhookCtx, c, e); err != nil {
if err := forwardMessageToWebhook(webhookCtx, c, e, sid); err != nil {
logrus.Error("Failed forward to webhook: ", err)
}
}(evt, client)
}(evt, client, sessionID)
}
}
Loading