]> git.giacomolli.uk - onechat/blobdiff - whatsapp_send.go
Send stored images to WhatsApp chats
[onechat] / whatsapp_send.go
index ce3f70b997d41a2e0fd55562a40634f8dfad94c6..b95a82eb6d53a594df8be45dbd2dee181738ac95 100644 (file)
@@ -16,8 +16,8 @@ import (
 )
 
 const (
 )
 
 const (
-       // whatsappSendTime is the longest time of a send.
-       whatsappSendTime = 30 * time.Second
+       // whatsappSendTimeout is the longest time of a send.
+       whatsappSendTimeout = 30 * time.Second
 
        // whatsappSendBytes is the most bytes of the text of a send.
        whatsappSendBytes = 65536
 
        // whatsappSendBytes is the most bytes of the text of a send.
        whatsappSendBytes = 65536
@@ -31,21 +31,24 @@ const (
        whatsappFailed  = "failed"
 )
 
        whatsappFailed  = "failed"
 )
 
-// whatsappSend sends text to chat, the ID of a private chat or a group, and
-// returns the answer of the tool. id names the send: a call with an id that has
-// a record gives the answer of the record and sends nothing, and the same id
-// with another chat or text is an error. The record "sending" is durable before
-// the send, so a stop of the daemon during the send gives an answer that the
-// result is unknown, and not a second send. A disconnected WhatsApp sends
-// nothing and makes no record. link gives the device, the client, and the own
-// IDs, groups the names of the groups, and state the send records. The clock
-// gives the times of the records and the time zone of the answer.
-func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups, state *WhatsAppState, clock func() time.Time, id, chat, text string) (string, error) {
+// whatsappSend sends text and an optional stored image to chat, the ID of a
+// private chat or a group, and returns the answer of the tool. id names the
+// send: a call with an id that has a record gives the answer of the record and
+// sends nothing, and the same id with another chat, text, or image is an error.
+// The record "sending" is durable before the send, so a stop of the daemon
+// during the send gives an answer that the result is unknown, and not a second
+// send. A disconnected WhatsApp sends nothing and makes no record. link gives
+// the device, the client, and the own IDs, groups the names of the groups, and
+// state the send records. The clock gives the times of the records and the time
+// zone of the answer. Image names are checked before the record is written, and
+// uploads start only after the record is durable. Text-only records keep their
+// text digest.
+func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups, state *WhatsAppState, images *ImageStore, clock func() time.Time, id, chat, text, image string) (string, error) {
        if !link.linked {
                return whatsappNotLinked, nil
        }
 
        if !link.linked {
                return whatsappNotLinked, nil
        }
 
-       jid, err := whatsappSendCheck(id, chat, text, link.own)
+       jid, err := whatsappSendCheck(id, chat, text, image, link.own)
        if err != nil {
                return "", err
        }
        if err != nil {
                return "", err
        }
@@ -53,6 +56,7 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
        digest := sha256.Sum256([]byte(text))
        record := WhatsAppSend{
                ID:     id,
        digest := sha256.Sum256([]byte(text))
        record := WhatsAppSend{
                ID:     id,
+               Image:  image,
                Chat:   chat,
                Digest: hex.EncodeToString(digest[:]),
                State:  whatsappSending,
                Chat:   chat,
                Digest: hex.EncodeToString(digest[:]),
                State:  whatsappSending,
@@ -61,7 +65,7 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
 
        held, found := state.Send(id)
        switch {
 
        held, found := state.Send(id)
        switch {
-       case found && (held.Chat != record.Chat || held.Digest != record.Digest):
+       case found && (held.Chat != record.Chat || held.Digest != record.Digest || held.Image != record.Image):
                return "", fmt.Errorf("the id %s belongs to another message: give a new id for each message", id)
        case found && held.State == whatsappSending:
                return "The send with this id started, but its result is unknown, because the daemon stopped during it. Ask the user to look at the chat in WhatsApp before you send the message again with a new id.", nil
                return "", fmt.Errorf("the id %s belongs to another message: give a new id for each message", id)
        case found && held.State == whatsappSending:
                return "The send with this id started, but its result is unknown, because the daemon stopped during it. Ask the user to look at the chat in WhatsApp before you send the message again with a new id.", nil
@@ -71,20 +75,39 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
                return "", errors.New("WhatsApp is disconnected, so nothing was sent: try again later")
        }
 
                return "", errors.New("WhatsApp is disconnected, so nothing was sent: try again later")
        }
 
+       var data []byte
+
+       if image != "" {
+               data, err = images.Read(image)
+               if err != nil {
+                       return "", fmt.Errorf("read image to send: %w", err)
+               }
+       }
+
        record.MessageID = link.client.GenerateMessageID()
        err = state.PutSend(record)
        if err != nil {
                return "", err
        }
 
        record.MessageID = link.client.GenerateMessageID()
        err = state.PutSend(record)
        if err != nil {
                return "", err
        }
 
-       sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTime)
+       message := &waE2E.Message{Conversation: new(text)}
+       if image != "" {
+               message, err = whatsappImageMessage(ctx, link.client, data, text)
+       }
+
+       sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTimeout)
        defer cancel()
 
        defer cancel()
 
-       response, err := link.client.SendMessage(sendCtx, jid, &waE2E.Message{
-               Conversation: new(text),
-       }, whatsmeow.SendRequestExtra{
-               ID: record.MessageID,
-       })
+       var response whatsmeow.SendResponse
+
+       if err == nil {
+               response, err = link.client.SendMessage(sendCtx, jid, message, whatsmeow.SendRequestExtra{
+                       ID: record.MessageID,
+               })
+               if err != nil && image != "" {
+                       err = fmt.Errorf("send image: %w", whatsappImageSendError(err))
+               }
+       }
 
        record.State = whatsappSent
        if err != nil {
 
        record.State = whatsappSent
        if err != nil {
@@ -108,14 +131,14 @@ func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups
 }
 
 // whatsappSendCheck returns the JID of chat, and an error when id, chat, or
 }
 
 // whatsappSendCheck returns the JID of chat, and an error when id, chat, or
-// text cannot go to a send. The chat is a private chat or a group, and not the
-// chat of the user with itself.
-func whatsappSendCheck(id, chat, text string, own []string) (types.JID, error) {
+// text cannot go to a send. Text can be empty only with an image. The chat is a
+// private chat or a group, and not the chat of the user with itself.
+func whatsappSendCheck(id, chat, text, image string, own []string) (types.JID, error) {
        jid, err := types.ParseJID(chat)
        switch {
        case strings.TrimSpace(id) == "" || len(id) > whatsappSendIDBytes:
                return types.EmptyJID, fmt.Errorf("the id must have 1 to %d bytes", whatsappSendIDBytes)
        jid, err := types.ParseJID(chat)
        switch {
        case strings.TrimSpace(id) == "" || len(id) > whatsappSendIDBytes:
                return types.EmptyJID, fmt.Errorf("the id must have 1 to %d bytes", whatsappSendIDBytes)
-       case strings.TrimSpace(text) == "" || len(text) > whatsappSendBytes:
+       case (strings.TrimSpace(text) == "" && image == "") || len(text) > whatsappSendBytes:
                return types.EmptyJID, fmt.Errorf("the text must have 1 to %d bytes", whatsappSendBytes)
        case err != nil || jid.User == "" || jid != jid.ToNonAD() || jid.String() != chat:
                return types.EmptyJID, fmt.Errorf("%q is not the ID of a WhatsApp chat: find the ID with whatsapp_find", chat)
                return types.EmptyJID, fmt.Errorf("the text must have 1 to %d bytes", whatsappSendBytes)
        case err != nil || jid.User == "" || jid != jid.ToNonAD() || jid.String() != chat:
                return types.EmptyJID, fmt.Errorf("%q is not the ID of a WhatsApp chat: find the ID with whatsapp_find", chat)