]> git.giacomolli.uk - onechat/commitdiff
Convert WhatsApp events with pure functions
authorAnderson A. Giacomolli <agiacomolli@gmail.com>
Sun, 8 Nov 2026 21:32:21 +0000 (18:32 -0300)
committerAnderson A. Giacomolli <agiacomolli@gmail.com>
Sun, 8 Nov 2026 21:32:27 +0000 (18:32 -0300)
The conversion of a message event to a batch message and the removal of
the own IDs from a text are now the functions whatsappMessage,
whatsappText, and whatsappRedact. They get the device and the own IDs as
arguments and read no state of WhatsApp, so the batches, find, and send
can use them after the split of WhatsApp by responsibility. The method
receive copies the device and the own IDs under the mutex, as message
did, and the method redact calls whatsappRedact. The behavior does not
change.

Issue: 6f69ca1

whatsapp.go

index 42b95a8b42bf08f7313f87dfc27bfc36ecf8966f..44b9dfa6557336a241348894e8f681c976ea9dc3 100644 (file)
@@ -1019,7 +1019,12 @@ func (w *WhatsApp) receive(event *events.Message) bool {
        ctx, cancel := context.WithTimeout(context.Background(), whatsappLookupTime)
        defer cancel()
 
-       chat, message, found := w.message(ctx, event)
+       w.mutex.Lock()
+       device := w.device
+       own := slices.Clone(w.own)
+       w.mutex.Unlock()
+
+       chat, message, found := whatsappMessage(ctx, device, own, event)
        if !found {
                return true
        }
@@ -1042,98 +1047,6 @@ func (w *WhatsApp) receive(event *events.Message) bool {
        return true
 }
 
-// message returns the chat and the batch message of event, or false when the
-// batches do not take it. They take the text of a private chat or a group, from
-// another person or from the user. They do not take media, reactions, edits,
-// deletions, view-once and disappearing messages, old history, status
-// broadcasts, channels, and the chat of the user with itself.
-//
-// A mention of the user becomes @you, another mention @NAME when the name is
-// known, and the own number and LID become OWN, so that they never go to the
-// log.
-func (w *WhatsApp) message(ctx context.Context, event *events.Message) (WhatsAppChat, WhatsAppMessage, bool) {
-       w.mutex.Lock()
-       device := w.device
-       own := slices.Clone(w.own)
-       w.mutex.Unlock()
-
-       info := event.Info
-       chatJID := info.Chat.ToNonAD()
-       switch {
-       case device == nil:
-       case event.IsEdit, info.Edit != "", event.IsEphemeral, event.IsViewOnce, event.SourceWebMsg != nil:
-       case chatJID.Server != types.DefaultUserServer && chatJID.Server != types.HiddenUserServer && chatJID.Server != types.GroupServer:
-       case slices.Contains(own, chatJID.User):
-       default:
-               return w.text(ctx, device, own, event)
-       }
-
-       return WhatsAppChat{}, WhatsAppMessage{}, false
-}
-
-// text returns the chat and the batch message of event, a message that the
-// batches take when it has text.
-func (w *WhatsApp) text(ctx context.Context, device *store.Device, own []string, event *events.Message) (WhatsAppChat, WhatsAppMessage, bool) {
-       info := event.Info
-       text := event.Message.GetConversation()
-       extended := event.Message.GetExtendedTextMessage()
-       if text == "" {
-               text = extended.GetText()
-       }
-
-       if strings.TrimSpace(text) == "" {
-               return WhatsAppChat{}, WhatsAppMessage{}, false
-       }
-
-       contextInfo := extended.GetContextInfo()
-       message := WhatsAppMessage{
-               ID:   info.ID,
-               Time: info.Timestamp,
-               Mine: info.IsFromMe,
-       }
-
-       if !message.Mine {
-               message.Sender = whatsappContactName(ctx, device, info.Sender, info.PushName)
-       }
-
-       for _, mentioned := range contextInfo.GetMentionedJID() {
-               jid, err := types.ParseJID(mentioned)
-               switch {
-               case err != nil:
-               case slices.Contains(own, jid.User):
-                       message.Mention = true
-                       text = strings.ReplaceAll(text, "@"+jid.User, "@you")
-               default:
-                       text = strings.ReplaceAll(text, "@"+jid.User, "@"+whatsappContactName(ctx, device, jid, ""))
-               }
-       }
-
-       participant, err := types.ParseJID(contextInfo.GetParticipant())
-       message.Reply = err == nil && slices.Contains(own, participant.User)
-
-       for _, id := range own {
-               text = strings.ReplaceAll(text, id, "OWN")
-       }
-
-       message.Text = text
-       chatJID := info.Chat.ToNonAD()
-       chat := WhatsAppChat{
-               ID:    chatJID.String(),
-               Group: chatJID.Server == types.GroupServer,
-       }
-
-       if !chat.Group {
-               push := info.PushName
-               if message.Mine {
-                       push = ""
-               }
-
-               chat.Name = whatsappContactName(ctx, device, chatJID, push)
-       }
-
-       return chat, message, true
-}
-
 // caughtUpBy ends the catch-up of the connection with the number catchups, when
 // no later connection started a new one.
 func (w *WhatsApp) caughtUpBy(catchups int) {
@@ -1201,11 +1114,7 @@ func (w *WhatsApp) redact(text string) string {
        w.mutex.Lock()
        defer w.mutex.Unlock()
 
-       for _, id := range w.own {
-               text = strings.ReplaceAll(text, id, "OWN")
-       }
-
-       return text
+       return whatsappRedact(w.own, text)
 }
 
 // Send sends text to chat, the ID of a private chat or a group, and returns the
@@ -1320,6 +1229,90 @@ func whatsappNewClient(device *store.Device, handle func(event any) bool) WhatsA
        return client
 }
 
+// whatsappMessage returns the chat and the batch message of event, or false
+// when the batches do not take it. device is the linked device, and own holds
+// the own IDs. The batches take the text of a private chat or a group, from
+// another person or from the user. They do not take media, reactions, edits,
+// deletions, view-once and disappearing messages, old history, status
+// broadcasts, channels, and the chat of the user with itself.
+//
+// A mention of the user becomes @you, another mention @NAME when the name is
+// known, and the own number and LID become OWN, so that they never go to the
+// log.
+func whatsappMessage(ctx context.Context, device *store.Device, own []string, event *events.Message) (WhatsAppChat, WhatsAppMessage, bool) {
+       info := event.Info
+       chatJID := info.Chat.ToNonAD()
+       switch {
+       case device == nil:
+       case event.IsEdit, info.Edit != "", event.IsEphemeral, event.IsViewOnce, event.SourceWebMsg != nil:
+       case chatJID.Server != types.DefaultUserServer && chatJID.Server != types.HiddenUserServer && chatJID.Server != types.GroupServer:
+       case slices.Contains(own, chatJID.User):
+       default:
+               return whatsappText(ctx, device, own, event)
+       }
+
+       return WhatsAppChat{}, WhatsAppMessage{}, false
+}
+
+// whatsappText returns the chat and the batch message of event, a message that
+// the batches take when it has text.
+func whatsappText(ctx context.Context, device *store.Device, own []string, event *events.Message) (WhatsAppChat, WhatsAppMessage, bool) {
+       info := event.Info
+       text := event.Message.GetConversation()
+       extended := event.Message.GetExtendedTextMessage()
+       if text == "" {
+               text = extended.GetText()
+       }
+
+       if strings.TrimSpace(text) == "" {
+               return WhatsAppChat{}, WhatsAppMessage{}, false
+       }
+
+       contextInfo := extended.GetContextInfo()
+       message := WhatsAppMessage{
+               ID:   info.ID,
+               Time: info.Timestamp,
+               Mine: info.IsFromMe,
+       }
+
+       if !message.Mine {
+               message.Sender = whatsappContactName(ctx, device, info.Sender, info.PushName)
+       }
+
+       for _, mentioned := range contextInfo.GetMentionedJID() {
+               jid, err := types.ParseJID(mentioned)
+               switch {
+               case err != nil:
+               case slices.Contains(own, jid.User):
+                       message.Mention = true
+                       text = strings.ReplaceAll(text, "@"+jid.User, "@you")
+               default:
+                       text = strings.ReplaceAll(text, "@"+jid.User, "@"+whatsappContactName(ctx, device, jid, ""))
+               }
+       }
+
+       participant, err := types.ParseJID(contextInfo.GetParticipant())
+       message.Reply = err == nil && slices.Contains(own, participant.User)
+
+       message.Text = whatsappRedact(own, text)
+       chatJID := info.Chat.ToNonAD()
+       chat := WhatsAppChat{
+               ID:    chatJID.String(),
+               Group: chatJID.Server == types.GroupServer,
+       }
+
+       if !chat.Group {
+               push := info.PushName
+               if message.Mine {
+                       push = ""
+               }
+
+               chat.Name = whatsappContactName(ctx, device, chatJID, push)
+       }
+
+       return chat, message, true
+}
+
 // whatsappContactName returns the name of the person jid: the full name, the
 // first name, the business name, or the push name of the contact in the session
 // of device, then push, the push name of a message, then the phone number. A
@@ -1433,6 +1426,15 @@ func whatsappOwnIDs(ids ...string) []string {
        return own
 }
 
+// whatsappRedact replaces each of the own IDs own in text with OWN.
+func whatsappRedact(own []string, text string) string {
+       for _, id := range own {
+               text = strings.ReplaceAll(text, id, "OWN")
+       }
+
+       return text
+}
+
 // whatsappSessionAddress returns the SQLite address of the session database at
 // path, with foreign keys on, as the library needs, and a wait for a busy
 // database.