+// receive puts the text of event into its batch and wakes Run. It returns false
+// when the state file cannot hold the message, so that the library does not
+// acknowledge it, and Run stops the daemon. A message that is not text, or that
+// the batches do not take, is handled with nothing to do.
+func (w *WhatsApp) receive(event *events.Message) bool {
+ ctx, cancel := context.WithTimeout(context.Background(), whatsappLookupTime)
+ defer cancel()
+
+ chat, message, found := w.message(ctx, event)
+ if !found {
+ return true
+ }
+
+ _, err := w.batches.Add(chat, message, time.Now())
+ if err != nil {
+ select {
+ case w.failures <- err:
+ default:
+ }
+
+ return false
+ }
+
+ select {
+ case w.wake <- struct{}{}:
+ default:
+ }
+
+ 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
+}
+