From: Anderson A. Giacomolli Date: Sun, 8 Nov 2026 21:32:21 +0000 (-0300) Subject: Convert WhatsApp events with pure functions X-Git-Url: https://git.giacomolli.uk/onechat/commitdiff_plain/0b97bb2cc4bc0b8f8fe27cc7aa3f9d1d89ed28c8?ds=sidebyside;hp=a85d214071706753f358f0af23cfd525c28dbfe0 Convert WhatsApp events with pure functions 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 --- diff --git a/whatsapp.go b/whatsapp.go index 42b95a8..44b9dfa 100644 --- a/whatsapp.go +++ b/whatsapp.go @@ -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.