import (
"context"
+ "crypto/sha256"
"database/sql"
"errors"
"fmt"
SendMessage(ctx context.Context, to types.JID, message *waE2E.Message, extra ...whatsmeow.SendRequestExtra) (whatsmeow.SendResponse, error)
}
-// openWhatsApp opens the WhatsApp session in the data directory dir. Without
-// the session file, WhatsApp is not linked. The session gives full access to
-// the account, so a file that another user can read is an error. The clock
-// gives the times of the status, logger gets the failures, and newClient makes
-// the client of a device with its event handler.
-func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logger *slog.Logger, newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient) (*WhatsApp, error) {
- w := &WhatsApp{
- ctx: ctx,
- dir: filepath.Join(dir, whatsappDirName),
- clock: clock,
- logger: logger,
- newClient: newClient,
- requests: make(chan WhatsAppRequest, whatsappRequests),
- logouts: make(chan struct{}, 1),
- groups: newWhatsAppGroups(logger),
- }
-
- var err error
-
- w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName))
- if err != nil {
- return nil, err
- }
-
- w.inbox = newWhatsAppInbox(w.state, clock, w.groups, &ImageStore{dir: filepath.Join(dir, imageDirName)}, logger)
-
- info, err := os.Stat(w.sessionPath())
- if errors.Is(err, os.ErrNotExist) {
- return w, nil
- }
-
- if err != nil {
- return nil, fmt.Errorf("read WhatsApp session: %w", err)
- }
-
- if info.Mode().Perm()&0o077 != 0 {
- return nil, fmt.Errorf("%s holds the WhatsApp session, so its mode must be 0600", filepath.Join(whatsappDirName, whatsappSessionFileName))
- }
-
- err = w.openSession(ctx)
- if err != nil {
- return nil, err
- }
-
- if w.device.ID != nil {
- w.linked = true
- w.inbox.waitForCatchup()
-
- w.own = whatsappOwnIDs(w.device.ID.User, w.device.LID.User)
- }
-
- return w, nil
-}
-
// WhatsApp is the link of the daemon to the WhatsApp account of the user, as a
// linked device. The session database of the library in dir holds the keys of
// the device. Run keeps a linked device connected, and the library connects
closed bool
}
+// 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 {
+ linked bool
+ device *store.Device
+ client WhatsAppClient
+ connected bool
+ own []string
+}
+
+// WhatsAppRequest is a pair or unlink command for Run, with the function that
+// gives its answers to the client that asked. A pairing has the phone number.
+type WhatsAppRequest struct {
+ phone string
+ unlink bool
+ answer func(text string)
+}
+
+// openWhatsApp opens the WhatsApp session in the data directory dir. Without
+// the session file, WhatsApp is not linked. The session gives full access to
+// the account, so a file that another user can read is an error. The clock
+// gives the times of the status, logger gets the failures, and newClient makes
+// the client of a device with its event handler.
+func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logger *slog.Logger, newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient) (*WhatsApp, error) {
+ w := &WhatsApp{
+ ctx: ctx,
+ dir: filepath.Join(dir, whatsappDirName),
+ clock: clock,
+ logger: logger,
+ newClient: newClient,
+ requests: make(chan WhatsAppRequest, whatsappRequests),
+ logouts: make(chan struct{}, 1),
+ groups: newWhatsAppGroups(logger),
+ }
+
+ var err error
+
+ w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName))
+ if err != nil {
+ return nil, err
+ }
+
+ w.inbox = newWhatsAppInbox(w.state, clock, w.groups, &ImageStore{dir: filepath.Join(dir, imageDirName)}, logger)
+
+ info, err := os.Stat(w.sessionPath())
+ if errors.Is(err, os.ErrNotExist) {
+ return w, nil
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("read WhatsApp session: %w", err)
+ }
+
+ if info.Mode().Perm()&0o077 != 0 {
+ return nil, fmt.Errorf("%s holds the WhatsApp session, so its mode must be 0600", filepath.Join(whatsappDirName, whatsappSessionFileName))
+ }
+
+ err = w.openSession(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ if w.device.ID != nil {
+ w.linked = true
+ w.inbox.waitForCatchup()
+
+ w.own = whatsappOwnIDs(w.device.ID.User, w.device.LID.User)
+ }
+
+ return w, nil
+}
+
// Run connects a linked device, logs the closed batches in l, runs the pair and
// unlink commands, and handles a logout from the phone, until ctx ends. Its
// notes go to the log of l, and notify gets the notices for all clients. A
// 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 whatsappRedact(w.own, text)
}
-// 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 {
- linked bool
- device *store.Device
- client WhatsAppClient
- connected bool
- own []string
-}
-
-// WhatsAppRequest is a pair or unlink command for Run, with the function that
-// gives its answers to the client that asked. A pairing has the phone number.
-type WhatsAppRequest struct {
- phone string
- unlink bool
- answer func(text string)
-}
-
// whatsappSetDeviceProps sets the device properties that the library sends at
// the pairing. They are global values of the library, so the daemon sets them
// once before any client exists. The phone shows the linked device with the
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 {