From c94b8fcf508ee349f92cef45c309c03800258acd Mon Sep 17 00:00:00 2001 From: AntonKun Date: Mon, 12 Jan 2026 17:03:20 +0200 Subject: [PATCH 1/4] Sending sent messages to the RabbitMQ queue --- handlers.go | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 2 deletions(-) diff --git a/handlers.go b/handlers.go index 0711ce83..4fda38fd 100644 --- a/handlers.go +++ b/handlers.go @@ -936,6 +936,11 @@ func (s *server) SendDocument() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "document", t.Caption, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1089,6 +1094,11 @@ func (s *server) SendAudio() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "audio", "", "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1276,6 +1286,11 @@ func (s *server) SendImage() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "image", t.Caption, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1412,6 +1427,11 @@ func (s *server) SendSticker() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "sticker", "", "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1568,6 +1588,11 @@ func (s *server) SendVideo() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "video", t.Caption, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1672,6 +1697,11 @@ func (s *server) SendContact() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "contact", t.Name, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1778,6 +1808,11 @@ func (s *server) SendLocation() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "location", t.Name, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -1872,16 +1907,22 @@ func (s *server) SendButtons() http.HandlerFunc { Buttons: buttons, } - resp, err = clientManager.GetWhatsmeowClient(txtid).SendMessage(context.Background(), recipient, &waE2E.Message{ViewOnceMessage: &waE2E.FutureProofMessage{ + msg := &waE2E.Message{ViewOnceMessage: &waE2E.FutureProofMessage{ Message: &waE2E.Message{ ButtonsMessage: msg2, }, - }}, whatsmeow.SendRequestExtra{ID: msgid}) + }} + resp, err = clientManager.GetWhatsmeowClient(txtid).SendMessage(context.Background(), recipient, msg, whatsmeow.SendRequestExtra{ID: msgid}) if err != nil { s.Respond(w, r, http.StatusInternalServerError, errors.New(fmt.Sprintf("error sending message: %v", err))) return } + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -2030,6 +2071,11 @@ func (s *server) SendList() http.HandlerFunc { return } + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message list sent") response := map[string]interface{}{ "Details": "Sent", @@ -2216,6 +2262,11 @@ func (s *server) SendMessage() http.HandlerFunc { historyLimit, _ := strconv.Atoi(historyStr) s.saveOutgoingMessageToHistory(txtid, recipient.String(), msgid, "text", t.Body, "", historyLimit) + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -2290,6 +2341,11 @@ func (s *server) SendPoll() http.HandlerFunc { return } + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, pollMessage, resp.Timestamp, "poll") + log.Info().Str("timestamp", fmt.Sprintf("%v", resp.Timestamp)).Str("id", msgid).Msg("Poll sent") response := map[string]interface{}{"Details": "Poll sent successfully", "Id": msgid} @@ -2720,6 +2776,11 @@ func (s *server) SendTemplate() http.HandlerFunc { return } + // Publish sent message event to RabbitMQ + token := r.Context().Value("userinfo").(Values).Get("Token") + userID := r.Context().Value("userinfo").(Values).Get("Id") + s.publishSentMessageEvent(token, userID, txtid, recipient, msgid, msg, resp.Timestamp) + log.Info().Str("timestamp", fmt.Sprintf("%d", resp.Timestamp.Unix())).Str("id", msgid).Msg("Message sent") response := map[string]interface{}{"Details": "Sent", "Timestamp": resp.Timestamp.Unix(), "Id": msgid} responseJson, err := json.Marshal(response) @@ -6478,3 +6539,165 @@ func (s *server) DownloadSticker() http.HandlerFunc { return } } + +// Helper function to determine message type from waE2E.Message +func (s *server) getMessageType(msg *waE2E.Message) string { + if msg.GetConversation() != "" { + return "text" + } + if msg.GetExtendedTextMessage() != nil { + return "text" + } + if msg.GetImageMessage() != nil { + return "image" + } + if msg.GetVideoMessage() != nil { + return "video" + } + if msg.GetAudioMessage() != nil { + return "audio" + } + if msg.GetDocumentMessage() != nil { + return "document" + } + if msg.GetStickerMessage() != nil { + return "sticker" + } + if msg.GetContactMessage() != nil { + return "contact" + } + if msg.GetLocationMessage() != nil { + return "location" + } + // Note: Poll messages are handled by BuildPollCreation and the type is determined from RawMessage + if msg.GetButtonsMessage() != nil || msg.GetButtonsResponseMessage() != nil { + return "buttons" + } + if msg.GetListMessage() != nil || msg.GetListResponseMessage() != nil { + return "list" + } + if msg.GetTemplateMessage() != nil { + return "template" + } + return "text" +} + +// publishSentMessageEvent creates and publishes a Message event for sent messages to RabbitMQ +// messageTypeOverride is optional - if provided, it will be used instead of auto-detecting the type +func (s *server) publishSentMessageEvent(token, userID, txtid string, recipient types.JID, msgid string, msg *waE2E.Message, timestamp time.Time, messageTypeOverride ...string) { + // Get the client to access store info + client := clientManager.GetWhatsmeowClient(txtid) + if client == nil { + return + } + + // Get sender JID (account owner) - use ToNonAD() to remove device ID, matching manual messages + var senderJID types.JID + if client.Store != nil && client.Store.ID != nil { + senderJID = client.Store.ID.ToNonAD() + } + + // Determine if it's a group + isGroup := recipient.Server == types.GroupServer || recipient.Server == types.BroadcastServer + + // Determine message type + messageType := s.getMessageType(msg) + if len(messageTypeOverride) > 0 && messageTypeOverride[0] != "" { + messageType = messageTypeOverride[0] + } + + // Get LIDs for SenderAlt and RecipientAlt (matching manual message format) + var senderLID types.JID + var recipientLID types.JID + if client.Store != nil && client.Store.LIDs != nil { + ctx := context.Background() + + // Get sender LID + if !senderJID.IsEmpty() { + if lid, err := client.Store.LIDs.GetLIDForPN(ctx, senderJID); err == nil && !lid.IsEmpty() { + senderLID = lid + } + } + + // Get recipient LID (only for non-group chats) + if !isGroup && !recipient.IsEmpty() { + if lid, err := client.Store.LIDs.GetLIDForPN(ctx, recipient); err == nil && !lid.IsEmpty() { + recipientLID = lid + } + } + } + + // Create MessageInfo structure + messageInfo := types.MessageInfo{ + MessageSource: types.MessageSource{ + Chat: recipient, + Sender: senderJID, + IsFromMe: true, + IsGroup: isGroup, + }, + ID: msgid, + Timestamp: timestamp, + Type: messageType, + } + + // Set SenderAlt and RecipientAlt (LIDs) + if !senderLID.IsEmpty() { + messageInfo.SenderAlt = senderLID + } + if !recipientLID.IsEmpty() { + messageInfo.RecipientAlt = recipientLID + } + + // Set DeviceSentMeta (matching manual message format) + messageInfo.DeviceSentMeta = &types.DeviceSentMeta{ + DestinationJID: recipient.String(), + Phash: "", + } + + // Get push name from store + if client.Store != nil && client.Store.PushName != "" { + messageInfo.PushName = client.Store.PushName + } + + // Wrap message in DeviceSentMessage structure for RawMessage (matching manual message format) + rawMessage := &waE2E.Message{ + DeviceSentMessage: &waE2E.DeviceSentMessage{ + DestinationJID: proto.String(recipient.String()), + Message: msg, + }, + } + + // Create the event structure matching whatsmeow's events.Message + messageEvent := map[string]interface{}{ + "Info": messageInfo, + "Message": msg, + "IsEphemeral": false, + "IsViewOnce": false, + "IsViewOnceV2": false, + "IsViewOnceV2Extension": false, + "IsDocumentWithCaption": false, + "IsLottieSticker": false, + "IsBotInvoke": false, + "IsEdit": false, + "SourceWebMsg": nil, + "UnavailableRequestID": "", + "RetryCount": 0, + "NewsletterMeta": nil, + "RawMessage": rawMessage, + } + + // Create the postmap structure that matches what sendEventWithWebHook expects + postmap := make(map[string]interface{}) + postmap["type"] = "Message" + postmap["event"] = messageEvent + + // Marshal to JSON + jsonData, err := json.Marshal(postmap) + if err != nil { + log.Error().Err(err).Msg("Failed to marshal sent message event to JSON") + return + } + + // Publish directly to RabbitMQ (bypassing subscription check for sent messages) + go sendToGlobalRabbit(jsonData, token, userID) +} From 835b36c06e91eb90055a36587459a1eefd36d8d6 Mon Sep 17 00:00:00 2001 From: AntonKun Date: Mon, 12 Jan 2026 17:37:37 +0200 Subject: [PATCH 2/4] Add the ability to dynamically select Platform Type --- main.go | 6 ++++++ wmiau.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 0d6d7238..a1a6ebe6 100755 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ var ( logType = flag.String("logtype", "console", "Type of log output (console or json)") skipMedia = flag.Bool("skipmedia", false, "Do not attempt to download media in messages") osName = flag.String("osname", "Mac OS 10", "Connection OSName in Whatsapp") + platformType = flag.String("platformtype", "DESKTOP", "Device platform type (DESKTOP, IPAD, ANDROID_TABLET, IOS_PHONE, ANDROID_PHONE, etc.)") colorOutput = flag.Bool("color", false, "Enable colored output for console logs") sslcert = flag.String("sslcertificate", "", "SSL Certificate File") sslprivkey = flag.String("sslprivatekey", "", "SSL Certificate Private Key File") @@ -225,6 +226,11 @@ func main() { *osName = v } + // Override platformType from environment variable if set + if v := os.Getenv("SESSION_PLATFORM_TYPE"); v != "" { + *platformType = v + } + if *versionFlag { fmt.Printf("WuzAPI version %s\n", version) os.Exit(0) diff --git a/wmiau.go b/wmiau.go index f34012ac..d04136ec 100644 --- a/wmiau.go +++ b/wmiau.go @@ -366,6 +366,64 @@ func parseJID(arg string) (types.JID, bool) { } } +// getPlatformTypeEnum converts a platform type string to the corresponding DeviceProps enum +// Returns DESKTOP as default if the string doesn't match any known type +func getPlatformTypeEnum(platformType string) *waCompanionReg.DeviceProps_PlatformType { + platformType = strings.ToUpper(strings.TrimSpace(platformType)) + + switch platformType { + case "UNKNOWN": + return waCompanionReg.DeviceProps_UNKNOWN.Enum() + case "CHROME": + return waCompanionReg.DeviceProps_CHROME.Enum() + case "FIREFOX": + return waCompanionReg.DeviceProps_FIREFOX.Enum() + case "IE": + return waCompanionReg.DeviceProps_IE.Enum() + case "OPERA": + return waCompanionReg.DeviceProps_OPERA.Enum() + case "SAFARI": + return waCompanionReg.DeviceProps_SAFARI.Enum() + case "EDGE": + return waCompanionReg.DeviceProps_EDGE.Enum() + case "DESKTOP": + return waCompanionReg.DeviceProps_DESKTOP.Enum() + case "IPAD": + return waCompanionReg.DeviceProps_IPAD.Enum() + case "ANDROID_TABLET": + return waCompanionReg.DeviceProps_ANDROID_TABLET.Enum() + case "OHANA": + return waCompanionReg.DeviceProps_OHANA.Enum() + case "ALOHA": + return waCompanionReg.DeviceProps_ALOHA.Enum() + case "CATALINA": + return waCompanionReg.DeviceProps_CATALINA.Enum() + case "TCL_TV": + return waCompanionReg.DeviceProps_TCL_TV.Enum() + case "IOS_PHONE": + return waCompanionReg.DeviceProps_IOS_PHONE.Enum() + case "IOS_CATALYST": + return waCompanionReg.DeviceProps_IOS_CATALYST.Enum() + case "ANDROID_PHONE": + return waCompanionReg.DeviceProps_ANDROID_PHONE.Enum() + case "ANDROID_AMBIGUOUS": + return waCompanionReg.DeviceProps_ANDROID_AMBIGUOUS.Enum() + case "WEAR_OS": + return waCompanionReg.DeviceProps_WEAR_OS.Enum() + case "AR_WRIST": + return waCompanionReg.DeviceProps_AR_WRIST.Enum() + case "AR_DEVICE": + return waCompanionReg.DeviceProps_AR_DEVICE.Enum() + case "UWP": + return waCompanionReg.DeviceProps_UWP.Enum() + case "VR": + return waCompanionReg.DeviceProps_VR.Enum() + default: + log.Warn().Str("platformType", platformType).Msg("Unknown platform type, defaulting to DESKTOP") + return waCompanionReg.DeviceProps_DESKTOP.Enum() + } +} + func (s *server) startClient(userID string, textjid string, token string, subscriptions []string) { log.Info().Str("userid", userID).Str("jid", textjid).Msg("Starting websocket connection to Whatsapp") @@ -407,7 +465,7 @@ func (s *server) startClient(userID string, textjid string, token string, subscr // Now we can use the client with the manager clientManager.SetWhatsmeowClient(userID, client) - store.DeviceProps.PlatformType = waCompanionReg.DeviceProps_DESKTOP.Enum() + store.DeviceProps.PlatformType = getPlatformTypeEnum(*platformType) store.DeviceProps.Os = osName mycli := MyClient{client, 1, userID, token, subscriptions, s.db, s} From 7c5a31a8226b647c8f4b20ca4faca17ac7f909df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 02:12:35 +0000 Subject: [PATCH 3/4] docs(README): update contributors --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fcfb0624..007015f1 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,13 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Igor Trindade + + + Anton +
+ Anton Kozyk +
+ Christopher @@ -439,6 +446,8 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) João Victor Souza + + Gustavo @@ -446,15 +455,6 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Gustavo Salomé - - - - - Anton -
- Anton Kozyk -
- Anil @@ -500,7 +500,7 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) - + Joseph
Joseph Fernandes From 3208d29e99cf393abd2a6e19750d34664276bcc5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 02:22:42 +0000 Subject: [PATCH 4/4] docs(README): update contributors --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 007015f1..d71214ac 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,13 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Nicolas
+ + + cleitonme/ +
+ cleitonme +
+ Guilherme @@ -337,13 +344,6 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Luiz Felipe Neves - - - cleitonme/ -
- cleitonme -
- Wellington @@ -367,6 +367,13 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) ramon-victor + + + Anton +
+ Anton Kozyk +
+ Netrix @@ -395,6 +402,8 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Vitor Silva Lima + + Ruan @@ -402,8 +411,6 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Ruan Kaylo - - Pedro @@ -418,13 +425,6 @@ Check the [API Reference](https://github.com/asternic/wuzapi/blob/main/API.md) Igor Trindade - - - Anton -
- Anton Kozyk -
- Christopher