import (
"context"
+ "crypto/sha256"
"database/sql"
"errors"
"fmt"
// whatsappTimeLayout is the layout of the times in the answers.
whatsappTimeLayout = "2006-01-02 15:04"
- // whatsappPairTime is the longest time of a pairing. The library closes
- // the pairing connection after about 160 seconds.
- whatsappPairTime = 3 * time.Minute
+ // whatsappPairTimeout is the longest time of a pairing. The library
+ // closes the pairing connection after about 160 seconds.
+ whatsappPairTimeout = 3 * time.Minute
- // whatsappLogoutTime is the longest time of the logout call of an
+ // whatsappLogoutTimeout is the longest time of the logout call of an
// unlink.
- whatsappLogoutTime = 30 * time.Second
+ whatsappLogoutTimeout = 30 * time.Second
- // whatsappNoticeDelay is how long a disconnection lasts before the user
- // gets a notice.
- whatsappNoticeDelay = 10 * time.Minute
+ // whatsappOutageNoticeDelay is the delay from a disconnection to its
+ // notice to the user.
+ whatsappOutageNoticeDelay = 10 * time.Minute
// whatsappRetryDelay is the first wait after a failed starting
// connection.
// wait for Run.
whatsappRequests = 8
- // whatsappLookupTime is the longest time of a read of the names in the
- // session database or of the groups from WhatsApp.
- whatsappLookupTime = 10 * time.Second
-
// 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."
)
Logout(ctx context.Context) error
GetJoinedGroups(ctx context.Context) ([]*types.GroupInfo, error)
Download(ctx context.Context, message whatsmeow.DownloadableMessage) ([]byte, error)
+ Upload(ctx context.Context, data []byte, mediaType whatsmeow.MediaType) (whatsmeow.UploadResponse, error)
GenerateMessageID() types.MessageID
SendMessage(ctx context.Context, to types.JID, message *waE2E.Message, extra ...whatsmeow.SendRequestExtra) (whatsmeow.SendResponse, error)
}
// 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,
- noticeDelay: whatsappNoticeDelay,
- requests: make(chan WhatsAppRequest, whatsappRequests),
- logouts: make(chan struct{}, 1),
- groups: newWhatsAppGroups(logger),
+ 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), whatsappBatchQuiet, whatsappBatchWindow)
+ w.state, err = openWhatsAppState(filepath.Join(w.dir, whatsappStateFileName))
if err != nil {
return nil, err
}
// log, and those of private chats hold phone numbers, because the agent needs
// them to send.
type WhatsApp struct {
- ctx context.Context
- dir string
- clock func() time.Time
- logger *slog.Logger
- newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient
- noticeDelay time.Duration
- requests chan WhatsAppRequest
- logouts chan struct{}
- state *WhatsAppState
- groups *WhatsAppGroups
- inbox *WhatsAppInbox
- routes *Routes
+ ctx context.Context
+ dir string
+ clock func() time.Time
+ logger *slog.Logger
+ newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient
+ requests chan WhatsAppRequest
+ logouts chan struct{}
+ state *WhatsAppState
+ groups *WhatsAppGroups
+ inbox *WhatsAppInbox
+ routes *Routes
sessionMutex sync.Mutex
mutex sync.Mutex
case w.isLinked():
request.answer("WhatsApp is linked already. Run /whatsapp unlink first.")
default:
- pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTime)
+ pairCtx, cancelPair := context.WithTimeout(ctx, whatsappPairTimeout)
cancel = cancelPair
pairing.Go(func() {
defer cancelPair()
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)
+// 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.logger, w.clock, id, chat, text, image)
}
func (w *WhatsApp) sessionPath() string {
w.disconnectedAt = w.clock()
w.disconnections++
disconnections := w.disconnections
- w.timer = time.AfterFunc(w.noticeDelay, func() {
+ w.timer = time.AfterFunc(whatsappOutageNoticeDelay, func() {
w.disconnectedLong(disconnections)
})
}
var logoutErr error
if client != nil {
- ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTime)
+ ctx, cancel := context.WithTimeout(ctx, whatsappLogoutTimeout)
defer cancel()
logoutErr = client.Logout(ctx)
// handle takes one event of the library and tells if it was handled. A message
// goes to the inbox. The names of the groups follow the events of the groups. A
// connection starts a catch-up of the inbox, which ends at OfflineSyncCompleted
-// or after its catchupLimit. A disconnection keeps the time of its start while
-// Run or the library tries again. After noticeDelay of a disconnection, all
-// clients get one notice, and they get one more when the connection comes back.
-// A disconnection that the library does not try again gives a notice at once.
+// or after whatsappCatchupTimeout. A disconnection keeps the time of its start
+// while Run or the library tries again. After whatsappOutageNoticeDelay of a
+// disconnection, all clients get one notice, and they get one more when the
+// connection comes back. A disconnection that the library does not try again
+// gives a notice at once.
//
// The log of the daemon gets one line for each change of the connection of the
// linked device: connected, a failed first connection, a lost connection, and
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 {