if err != nil {
return "", fmt.Errorf("show Telegram image: %w", err)
}
+
+ s.bot.logger.Info("image sent")
}
return imageText([]string{name}, caption) + "\n" + path, nil
require.Equal(t, "A red point.", calls[0].params["caption"])
require.Equal(t, name, calls[0].params["photo_name"])
require.Equal(t, picture, calls[0].params["photo"])
- require.Empty(t, logs.String())
+ require.Equal(t, "<6>image sent\n", logs.String())
}
func TestImageShowRefusesUnknownNamesBeforeSending(t *testing.T) {
import (
"context"
+ "crypto/sha256"
"database/sql"
"errors"
"fmt"
// Send returns the answer of whatsapp_send for text and an optional stored
// image to chat, with the durable send id that whatsappSend describes.
func (w *WhatsApp) Send(ctx context.Context, id, chat, text, image string) (string, error) {
- return whatsappSend(ctx, w.link(), w.groups, w.state, w.inbox.images, w.clock, id, chat, text, image)
+ return whatsappSend(ctx, w.link(), w.groups, w.state, w.inbox.images, w.logger, w.clock, id, chat, text, image)
}
func (w *WhatsApp) sessionPath() string {
return own
}
+// whatsappChatHash returns a short hash of chat for the daemon log, so that a
+// log line can tell chats apart without a phone number.
+func whatsappChatHash(chat types.JID) string {
+ digest := sha256.Sum256([]byte(chat.ToNonAD().String()))
+
+ return fmt.Sprintf("%x", digest[:6])
+}
+
// whatsappRedact replaces each of the own IDs own in text with OWN.
func whatsappRedact(own []string, text string) string {
for _, id := range own {
import (
"context"
- "crypto/sha256"
"fmt"
"log/slog"
"slices"
name, err := whatsappImage(ctx, link.client, i.images, event.Message.GetImageMessage())
if err != nil {
chat := event.Info.Chat.ToNonAD()
- digest := sha256.Sum256([]byte(chat.String()))
redact := append(slices.Clone(link.own), chat.User)
- i.logger.Error("image receive failed", "chat", fmt.Sprintf("%x", digest[:6]), "err", whatsappRedact(redact, err.Error()))
+ i.logger.Error("image receive failed", "chat", whatsappChatHash(chat), "err", whatsappRedact(redact, err.Error()))
text := "(image not downloaded)"
if caption != "" {
"encoding/hex"
"errors"
"fmt"
+ "log/slog"
"slices"
"strings"
"time"
// 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
+// state the send records, and logger gets one line for each send. 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) {
+func whatsappSend(ctx context.Context, link WhatsAppLink, groups *WhatsAppGroups, state *WhatsAppState, images *ImageStore, logger *slog.Logger, clock func() time.Time, id, chat, text, image string) (string, error) {
if !link.linked {
return whatsappNotLinked, nil
}
if err != nil {
record.State = whatsappFailed
record.Answer = "The send failed: " + whatsappRedact(link.own, err.Error()) + "."
+ logger.Warn("message send failed", "chat", whatsappChatHash(jid), "image", image != "", "err", whatsappRedact(append(slices.Clone(link.own), jid.User), err.Error()))
} else {
+ logger.Info("message sent", "chat", whatsappChatHash(jid), "image", image != "")
+
name := groups.name(ctx, link, jid.String())
if jid.Server != types.GroupServer {
name = whatsappContactName(ctx, link.device, jid, "")
require.Len(t, fake.Sent(), 1)
}
+func TestWhatsAppSendLogsEachSendWithoutTextOrNumbers(t *testing.T) {
+ dir := t.TempDir()
+ whatsappTestSession(t, dir, true)
+
+ whatsapp, fake, logs := whatsappStartTest(t, dir, newTestClock())
+ daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
+ whatsappTestConnect(t, fake)
+
+ name, err := whatsapp.inbox.images.Save(imageTestPNG(t, 4, 3))
+ require.NoError(t, err)
+
+ _, err = whatsapp.Send(t.Context(), "keys", whatsappTestAna.String(), "The keys are under the mat.", "")
+ require.NoError(t, err)
+
+ _, err = whatsapp.Send(t.Context(), "plan", whatsappTestAna.String(), "The plan.", name)
+ require.NoError(t, err)
+
+ fake.sendErr = errors.New("server error for " + whatsappTestNumber)
+ _, err = whatsapp.Send(t.Context(), "late", whatsappTestAna.String(), "Late.", "")
+ require.NoError(t, err)
+
+ chat := whatsappChatHash(whatsappTestAna)
+ require.Contains(t, logs.String(), "<6>message sent chat="+chat+" image=false\n")
+ require.Contains(t, logs.String(), "<6>message sent chat="+chat+" image=true\n")
+ require.Contains(t, logs.String(), "<4>message send failed chat="+chat+" image=false: server error for OWN\n")
+
+ for _, secret := range []string{"keys", "plan", "Late", whatsappTestNumber, whatsappTestAna.User} {
+ require.NotContains(t, logs.String(), secret)
+ }
+}
+
func TestWhatsAppSendCutByAStop(t *testing.T) {
whatsapp, fake := whatsappStartTestLinked(t)
daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
require.Equal(t, name, record.Image)
require.Equal(t, whatsappSent, record.State)
- again, err := whatsappSend(t.Context(), whatsapp.link(), whatsapp.groups, state, whatsapp.inbox.images, whatsapp.clock, "photo", whatsappTestAna.String(), "The plan.", name)
+ again, err := whatsappSend(t.Context(), whatsapp.link(), whatsapp.groups, state, whatsapp.inbox.images, discardLog(), whatsapp.clock, "photo", whatsappTestAna.String(), "The plan.", name)
require.NoError(t, err)
require.Contains(t, again, "nothing was sent again")
require.Len(t, fake.Uploads(), 1)
calls := telegram.Calls()
require.Equal(t, "sendPhoto", calls[len(calls)-1].method)
require.Equal(t, picture, calls[len(calls)-1].params["photo"])
- require.Empty(t, logs.String())
+ require.Equal(t, "<6>image sent\n", logs.String())
}
func TestWhatsAppImageUploadEndsWithItsCallerOrTimeout(t *testing.T) {