import (
"context"
- "crypto/sha256"
- "encoding/hex"
"errors"
"fmt"
"log"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
waLog "go.mau.fi/whatsmeow/util/log"
-
// The session store of the library opens SQLite with this pure Go
// driver.
_ "modernc.org/sqlite"
// whatsappNotLinked is the answer of a WhatsApp tool without a link.
whatsappNotLinked = "WhatsApp is not linked. Tell the user to run /whatsapp pair PHONE in the terminal or in Telegram."
-
- // whatsappSendTime is the longest time of a send.
- whatsappSendTime = 30 * time.Second
-
- // whatsappSendBytes is the most bytes of the text of a send.
- whatsappSendBytes = 65536
-
- // whatsappSendIDBytes is the most bytes of the ID of a send.
- whatsappSendIDBytes = 100
-
- // The states of a send record.
- whatsappSending = "sending"
- whatsappSent = "sent"
- whatsappFailed = "failed"
)
// WhatsAppClient is the part of the WhatsApp client that the daemon uses. The
return whatsappFind(ctx, w.link(), w.groups, name)
}
+// Send returns the answer of whatsapp_send for the send id of text to chat, as
+// whatsappSend says.
+func (w *WhatsApp) Send(ctx context.Context, id, chat, text string) (string, error) {
+ return whatsappSend(ctx, w.link(), w.groups, w.state, w.clock, id, chat, text)
+}
+
func (w *WhatsApp) sessionPath() string {
return filepath.Join(w.dir, whatsappSessionFileName)
}
return whatsappRedact(w.own, text)
}
-// Send 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.
-func (w *WhatsApp) Send(ctx context.Context, id, chat, text string) (string, error) {
- w.mutex.Lock()
- linked := w.linked
- device := w.device
- client := w.client
- connected := w.connected
- own := slices.Clone(w.own)
- w.mutex.Unlock()
-
- if !linked {
- return whatsappNotLinked, nil
- }
-
- jid, err := whatsappSendCheck(id, chat, text, own)
- if err != nil {
- return "", err
- }
-
- digest := sha256.Sum256([]byte(text))
- record := WhatsAppSend{
- ID: id,
- Chat: chat,
- Digest: hex.EncodeToString(digest[:]),
- State: whatsappSending,
- Time: w.clock(),
- }
-
- held, found := w.state.Send(id)
- switch {
- case found && (held.Chat != record.Chat || held.Digest != record.Digest):
- 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
- case found:
- return held.Answer + " This is the answer of the first call with this id: nothing was sent again.", nil
- case client == nil || !connected:
- return "", errors.New("WhatsApp is disconnected, so nothing was sent: try again later")
- }
-
- record.MessageID = client.GenerateMessageID()
- err = w.state.PutSend(record)
- if err != nil {
- return "", err
- }
-
- sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTime)
- defer cancel()
-
- response, err := client.SendMessage(sendCtx, jid, &waE2E.Message{
- Conversation: new(text),
- }, whatsmeow.SendRequestExtra{
- ID: record.MessageID,
- })
-
- record.State = whatsappSent
- if err != nil {
- record.State = whatsappFailed
- record.Answer = "The send failed: " + w.redact(err.Error()) + "."
- } else {
- name := w.groups.name(ctx, w.link(), jid.String())
- if jid.Server != types.GroupServer {
- name = whatsappContactName(ctx, device, jid, "")
- }
-
- record.Answer = "Sent to " + name + " at " + response.Timestamp.In(w.clock().Location()).Format("15:04") + "."
- }
-
- err = w.state.PutSend(record)
- if err != nil {
- return "", err
- }
-
- return record.Answer, nil
-}
-
// WhatsAppLink is a copy of the link of the device at one time, for the parts
// of WhatsApp that do not own it: the groups, the batches, find, and send.
type WhatsAppLink struct {
}
}
-// 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) {
- 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:
- 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)
- case jid.Server != types.DefaultUserServer && jid.Server != types.HiddenUserServer && jid.Server != types.GroupServer:
- return types.EmptyJID, fmt.Errorf("%q is not a private chat or a group", chat)
- case slices.Contains(own, jid.User):
- return types.EmptyJID, errors.New("this is the chat of the user with itself: send to another chat")
- }
-
- return jid, nil
-}
-
// whatsappOwnIDs returns the own number and the own LID that are not empty.
func whatsappOwnIDs(ids ...string) []string {
var own []string
--- /dev/null
+package main
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "slices"
+ "strings"
+ "time"
+
+ "go.mau.fi/whatsmeow"
+ "go.mau.fi/whatsmeow/proto/waE2E"
+ "go.mau.fi/whatsmeow/types"
+)
+
+const (
+ // whatsappSendTime is the longest time of a send.
+ whatsappSendTime = 30 * time.Second
+
+ // whatsappSendBytes is the most bytes of the text of a send.
+ whatsappSendBytes = 65536
+
+ // whatsappSendIDBytes is the most bytes of the ID of a send.
+ whatsappSendIDBytes = 100
+
+ // The states of a send record.
+ whatsappSending = "sending"
+ whatsappSent = "sent"
+ 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) {
+ if !link.linked {
+ return whatsappNotLinked, nil
+ }
+
+ jid, err := whatsappSendCheck(id, chat, text, link.own)
+ if err != nil {
+ return "", err
+ }
+
+ digest := sha256.Sum256([]byte(text))
+ record := WhatsAppSend{
+ ID: id,
+ Chat: chat,
+ Digest: hex.EncodeToString(digest[:]),
+ State: whatsappSending,
+ Time: clock(),
+ }
+
+ held, found := state.Send(id)
+ switch {
+ case found && (held.Chat != record.Chat || held.Digest != record.Digest):
+ 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
+ case found:
+ return held.Answer + " This is the answer of the first call with this id: nothing was sent again.", nil
+ case link.client == nil || !link.connected:
+ return "", errors.New("WhatsApp is disconnected, so nothing was sent: try again later")
+ }
+
+ record.MessageID = link.client.GenerateMessageID()
+ err = state.PutSend(record)
+ if err != nil {
+ return "", err
+ }
+
+ sendCtx, cancel := context.WithTimeout(ctx, whatsappSendTime)
+ defer cancel()
+
+ response, err := link.client.SendMessage(sendCtx, jid, &waE2E.Message{
+ Conversation: new(text),
+ }, whatsmeow.SendRequestExtra{
+ ID: record.MessageID,
+ })
+
+ record.State = whatsappSent
+ if err != nil {
+ record.State = whatsappFailed
+ record.Answer = "The send failed: " + whatsappRedact(link.own, err.Error()) + "."
+ } else {
+ name := groups.name(ctx, link, jid.String())
+ if jid.Server != types.GroupServer {
+ name = whatsappContactName(ctx, link.device, jid, "")
+ }
+
+ record.Answer = "Sent to " + name + " at " + response.Timestamp.In(clock().Location()).Format("15:04") + "."
+ }
+
+ err = state.PutSend(record)
+ if err != nil {
+ return "", err
+ }
+
+ return record.Answer, nil
+}
+
+// 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) {
+ 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:
+ 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)
+ case jid.Server != types.DefaultUserServer && jid.Server != types.HiddenUserServer && jid.Server != types.GroupServer:
+ return types.EmptyJID, fmt.Errorf("%q is not a private chat or a group", chat)
+ case slices.Contains(own, jid.User):
+ return types.EmptyJID, errors.New("this is the chat of the user with itself: send to another chat")
+ }
+
+ return jid, nil
+}
--- /dev/null
+package main
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+ "net/http"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/types/events"
+)
+
+func TestWhatsAppSendSendsOnceForEachID(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ whatsappTestContacts(t, whatsapp)
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "The keys are under the mat.")
+ require.NoError(t, err)
+ require.Equal(t, "Sent to Ana Souza at 12:05.", answer)
+
+ answer, err = whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "The keys are under the mat.")
+ require.NoError(t, err)
+ require.Equal(t, "Sent to Ana Souza at 12:05. This is the answer of the first call with this id: nothing was sent again.", answer)
+
+ _, err = whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "Another text.")
+ require.EqualError(t, err, "the id keys belongs to another message: give a new id for each message")
+
+ require.Equal(t, []WhatsAppFakeSend{
+ {
+ to: whatsappTestAna.String(),
+ text: "The keys are under the mat.",
+ id: "MSG1",
+ },
+ }, fake.Sent())
+
+ again, err := openWhatsAppState(filepath.Join(whatsapp.dir, whatsappStateFileName), whatsappBatchQuiet, whatsappBatchWindow)
+ require.NoError(t, err)
+
+ record, found := again.Send("keys")
+ require.True(t, found)
+ require.Equal(t, whatsappSent, record.State)
+ require.Equal(t, "MSG1", record.MessageID)
+}
+
+func TestWhatsAppSendToAGroup(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ fake.groups = []*types.GroupInfo{
+ {
+ JID: whatsappTestFamily,
+ GroupName: types.GroupName{
+ Name: "Family",
+ },
+ },
+ }
+
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ answer, err := whatsapp.Send(t.Context(), "cake", whatsappTestFamily.String(), "I bring the cake.")
+ require.NoError(t, err)
+ require.Equal(t, "Sent to Family at 12:05.", answer)
+}
+
+func TestWhatsAppSendThatFails(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ fake.sendErr = errors.New("server error for " + whatsappTestNumber)
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ for range 2 {
+ answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
+ require.NoError(t, err)
+ require.True(t, strings.HasPrefix(answer, "The send failed: server error for OWN."), answer)
+ }
+
+ require.Len(t, fake.Sent(), 1)
+}
+
+func TestWhatsAppSendCutByAStop(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ digest := sha256.Sum256([]byte("hi"))
+ require.NoError(t, whatsapp.state.PutSend(WhatsAppSend{
+ ID: "keys",
+ Chat: whatsappTestAna.String(),
+ Digest: hex.EncodeToString(digest[:]),
+ State: whatsappSending,
+ }))
+
+ answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
+ require.NoError(t, err)
+ require.Contains(t, answer, "its result is unknown")
+ require.Empty(t, fake.Sent())
+}
+
+func TestWhatsAppSendRefuses(t *testing.T) {
+ testCases := []struct {
+ name string
+ id string
+ chat string
+ text string
+ err string
+ }{
+ {
+ name: "no id",
+ id: " ",
+ chat: whatsappTestAna.String(),
+ text: "hi",
+ err: "the id must have 1 to 100 bytes",
+ },
+ {
+ name: "no text",
+ id: "a",
+ chat: whatsappTestAna.String(),
+ text: "",
+ err: "the text must have 1 to 65536 bytes",
+ },
+ {
+ name: "a name for a chat",
+ id: "a",
+ chat: "Ana Souza",
+ text: "hi",
+ err: `"Ana Souza" is not the ID of a WhatsApp chat: find the ID with whatsapp_find`,
+ },
+ {
+ name: "a status broadcast",
+ id: "a",
+ chat: types.StatusBroadcastJID.String(),
+ text: "hi",
+ err: `"status@broadcast" is not a private chat or a group`,
+ },
+ {
+ name: "the chat of the user",
+ id: "a",
+ chat: whatsappTestOwn.String(),
+ text: "hi",
+ err: "this is the chat of the user with itself: send to another chat",
+ },
+ }
+
+ whatsapp, fake := whatsappStartTestLinked(t)
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := whatsapp.Send(t.Context(), tc.id, tc.chat, tc.text)
+ require.EqualError(t, err, tc.err)
+ })
+ }
+
+ require.Empty(t, fake.Sent())
+}
+
+func TestWhatsAppSendWhileDisconnected(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+ require.True(t, fake.Emit(&events.Disconnected{}))
+
+ _, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
+ require.EqualError(t, err, "WhatsApp is disconnected, so nothing was sent: try again later")
+
+ require.True(t, fake.Emit(&events.Connected{}))
+ answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
+ require.NoError(t, err)
+ require.Equal(t, "Sent to +15550002222 at 12:05.", answer)
+}
+
+func TestWhatsAppSendWithoutALink(t *testing.T) {
+ whatsapp, _, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
+
+ answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
+ require.NoError(t, err)
+ require.Equal(t, whatsappNotLinked, answer)
+}
+
+func TestAgentSendsAWhatsAppMessage(t *testing.T) {
+ whatsapp, fake := whatsappStartTestLinked(t)
+ whatsappTestContacts(t, whatsapp)
+ server := newModelFake(t)
+ server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
+ if call.number == 0 {
+ modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "whatsapp_send", `{"id":"keys","chat":"15550002222@s.whatsapp.net","text":"The keys are under the mat."}`)}, 100, 0, 5))
+ return
+ }
+
+ modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Sent.")}, 100, 0, 5))
+ }
+
+ socket, _ := daemonStartTestWithWhatsApp(t, server, nil, whatsapp)
+ client := terminalStartTestClient(t, socket)
+ whatsappTestConnect(t, fake)
+
+ client.Type("Tell Ana that the keys are under the mat.")
+ client.WaitFor(t, "[result] Sent to Ana Souza at 12:05.\nSent.\n[done]")
+ require.Len(t, fake.Sent(), 1)
+}
package main
import (
- "crypto/sha256"
- "encoding/hex"
"errors"
- "net/http"
"os"
"path/filepath"
"strings"
require.Equal(t, `error: unknown tool "whatsapp_find"`, box.run(t.Context(), "whatsapp_find", `{"name":"ana"}`))
require.Equal(t, `error: unknown tool "whatsapp_send"`, box.run(t.Context(), "whatsapp_send", `{"id":"a","chat":"15550002222@s.whatsapp.net","text":"hi"}`))
}
-
-func TestWhatsAppSendSendsOnceForEachID(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- whatsappTestContacts(t, whatsapp)
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
-
- answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "The keys are under the mat.")
- require.NoError(t, err)
- require.Equal(t, "Sent to Ana Souza at 12:05.", answer)
-
- answer, err = whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "The keys are under the mat.")
- require.NoError(t, err)
- require.Equal(t, "Sent to Ana Souza at 12:05. This is the answer of the first call with this id: nothing was sent again.", answer)
-
- _, err = whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "Another text.")
- require.EqualError(t, err, "the id keys belongs to another message: give a new id for each message")
-
- require.Equal(t, []WhatsAppFakeSend{
- {
- to: whatsappTestAna.String(),
- text: "The keys are under the mat.",
- id: "MSG1",
- },
- }, fake.Sent())
-
- again, err := openWhatsAppState(filepath.Join(whatsapp.dir, whatsappStateFileName), whatsappBatchQuiet, whatsappBatchWindow)
- require.NoError(t, err)
-
- record, found := again.Send("keys")
- require.True(t, found)
- require.Equal(t, whatsappSent, record.State)
- require.Equal(t, "MSG1", record.MessageID)
-}
-
-func TestWhatsAppSendToAGroup(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- fake.groups = []*types.GroupInfo{
- {
- JID: whatsappTestFamily,
- GroupName: types.GroupName{
- Name: "Family",
- },
- },
- }
-
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
-
- answer, err := whatsapp.Send(t.Context(), "cake", whatsappTestFamily.String(), "I bring the cake.")
- require.NoError(t, err)
- require.Equal(t, "Sent to Family at 12:05.", answer)
-}
-
-func TestWhatsAppSendThatFails(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- fake.sendErr = errors.New("server error for " + whatsappTestNumber)
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
-
- for range 2 {
- answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
- require.NoError(t, err)
- require.True(t, strings.HasPrefix(answer, "The send failed: server error for OWN."), answer)
- }
-
- require.Len(t, fake.Sent(), 1)
-}
-
-func TestWhatsAppSendCutByAStop(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
-
- digest := sha256.Sum256([]byte("hi"))
- require.NoError(t, whatsapp.state.PutSend(WhatsAppSend{
- ID: "keys",
- Chat: whatsappTestAna.String(),
- Digest: hex.EncodeToString(digest[:]),
- State: whatsappSending,
- }))
-
- answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
- require.NoError(t, err)
- require.Contains(t, answer, "its result is unknown")
- require.Empty(t, fake.Sent())
-}
-
-func TestWhatsAppSendRefuses(t *testing.T) {
- testCases := []struct {
- name string
- id string
- chat string
- text string
- err string
- }{
- {
- name: "no id",
- id: " ",
- chat: whatsappTestAna.String(),
- text: "hi",
- err: "the id must have 1 to 100 bytes",
- },
- {
- name: "no text",
- id: "a",
- chat: whatsappTestAna.String(),
- text: "",
- err: "the text must have 1 to 65536 bytes",
- },
- {
- name: "a name for a chat",
- id: "a",
- chat: "Ana Souza",
- text: "hi",
- err: `"Ana Souza" is not the ID of a WhatsApp chat: find the ID with whatsapp_find`,
- },
- {
- name: "a status broadcast",
- id: "a",
- chat: types.StatusBroadcastJID.String(),
- text: "hi",
- err: `"status@broadcast" is not a private chat or a group`,
- },
- {
- name: "the chat of the user",
- id: "a",
- chat: whatsappTestOwn.String(),
- text: "hi",
- err: "this is the chat of the user with itself: send to another chat",
- },
- }
-
- whatsapp, fake := whatsappStartTestLinked(t)
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- _, err := whatsapp.Send(t.Context(), tc.id, tc.chat, tc.text)
- require.EqualError(t, err, tc.err)
- })
- }
-
- require.Empty(t, fake.Sent())
-}
-
-func TestWhatsAppSendWhileDisconnected(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
- whatsappTestConnect(t, fake)
- require.True(t, fake.Emit(&events.Disconnected{}))
-
- _, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
- require.EqualError(t, err, "WhatsApp is disconnected, so nothing was sent: try again later")
-
- require.True(t, fake.Emit(&events.Connected{}))
- answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
- require.NoError(t, err)
- require.Equal(t, "Sent to +15550002222 at 12:05.", answer)
-}
-
-func TestWhatsAppSendWithoutALink(t *testing.T) {
- whatsapp, _, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
-
- answer, err := whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "hi")
- require.NoError(t, err)
- require.Equal(t, whatsappNotLinked, answer)
-}
-
-func TestAgentSendsAWhatsAppMessage(t *testing.T) {
- whatsapp, fake := whatsappStartTestLinked(t)
- whatsappTestContacts(t, whatsapp)
- server := newModelFake(t)
- server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
- if call.number == 0 {
- modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "whatsapp_send", `{"id":"keys","chat":"15550002222@s.whatsapp.net","text":"The keys are under the mat."}`)}, 100, 0, 5))
- return
- }
-
- modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Sent.")}, 100, 0, 5))
- }
-
- socket, _ := daemonStartTestWithWhatsApp(t, server, nil, whatsapp)
- client := terminalStartTestClient(t, socket)
- whatsappTestConnect(t, fake)
-
- client.Type("Tell Ana that the keys are under the mat.")
- client.WaitFor(t, "[result] Sent to Ana Souza at 12:05.\nSent.\n[done]")
- require.Len(t, fake.Sent(), 1)
-}