+ answer("The connection to WhatsApp failed: " + err.Error())
+
+ return
+ }
+
+ select {
+ case item, open := <-qr:
+ if !open || item.Event != whatsmeow.QRChannelEventCode {
+ answer("WhatsApp did not start the pairing. Try again later.")
+
+ return
+ }
+ case <-ctx.Done():
+ if errors.Is(ctx.Err(), context.DeadlineExceeded) {
+ answer("The connection to WhatsApp did not start the pairing in time. Try again later.")
+ }
+
+ return
+ }
+
+ // The server accepts only a display name of the form "Browser (OS)"
+ // with a common browser and OS, and refuses the pairing otherwise.
+ code, err := client.PairPhone(ctx, phone, true, whatsmeow.PairClientChrome, "Chrome (Linux)")
+ if err != nil {
+ answer("WhatsApp refused the pairing: " + err.Error() + ". Check the phone number and run /whatsapp pair PHONE again.")
+
+ return
+ }
+
+ answer(whatsappPairSteps)
+ answer(code)
+
+ for {
+ var item whatsmeow.QRChannelItem
+
+ open := true
+ select {
+ case item, open = <-qr:
+ case <-ctx.Done():
+ if errors.Is(ctx.Err(), context.DeadlineExceeded) {
+ answer(whatsappPairExpired)
+ }
+
+ return
+ }
+
+ switch {
+ case !open || item.Event == whatsmeow.QRChannelTimeout.Event:
+ answer(whatsappPairExpired)
+
+ return
+ case item.Event == whatsmeow.QRChannelEventCode:
+ continue
+ case item.Event == whatsmeow.QRChannelSuccess.Event:
+ linked = true
+
+ w.linkedBy(client)
+ w.logger.Info("linked")
+
+ err = l.note("WhatsApp is linked.")
+ if err != nil {
+ w.logger.Error("chat note failed", "err", err)
+ }
+
+ answer("WhatsApp is linked to +" + phone + ".")
+
+ return
+ default:
+ answer("The pairing failed: " + whatsappItemError(item) + ". Run /whatsapp pair PHONE again.")
+
+ return
+ }
+ }
+}
+
+// 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 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
+// connected again with the length of the outage. The retries of the library and
+// Run between them log nothing.
+func (w *WhatsApp) handle(event any) bool {
+ w.mutex.Lock()
+
+ ctx := w.ctx
+ w.mutex.Unlock()
+
+ switch event := event.(type) {
+ case *events.Message:
+ return w.inbox.receive(ctx, w.link(), event)
+ case *events.GroupInfo:
+ if event.Name != nil {
+ w.groups.set(event.JID.String(), event.Name.Name)
+ }
+
+ return true
+ case *events.JoinedGroup:
+ w.groups.set(event.JID.String(), event.Name)
+
+ return true
+ case *events.OfflineSyncCompleted:
+ w.inbox.endCatchup()
+
+ return true
+ case *events.Connected:
+ w.inbox.startCatchup()
+ }
+
+ var notice, line string
+
+ level := slog.LevelInfo
+
+ var attrs []any
+
+ w.mutex.Lock()
+
+ switch event := event.(type) {
+ case *events.PairSuccess:
+ w.own = whatsappOwnIDs(event.ID.User, event.LID.User)
+ case *events.Connected:
+ switch {
+ case !w.linked || w.connected:
+ case w.disconnectedAt.IsZero():
+ line = "connected"
+ default:
+ line = "connected again"
+ attrs = append(attrs, "after", w.clock().Sub(w.disconnectedAt))
+ }
+
+ w.connected = true
+ w.retrying = false
+ w.connectedAt = w.clock()
+ if w.timer != nil {
+ w.timer.Stop()
+ }
+
+ if w.noticed {
+ w.noticed = false
+ notice = "WhatsApp is connected again."
+ }
+ case *events.Disconnected:
+ connected := w.connected
+ if w.startOutage() {
+ level = slog.LevelWarn
+ line = "disconnected, retrying"
+ if !connected {
+ line = "first connect failed, retrying"
+ }
+ }
+ case *events.LoggedOut:
+ w.retrying = false
+ select {
+ case w.logouts <- struct{}{}:
+ default:
+ }
+ case events.PermanentDisconnect:
+ w.retrying = false
+ if w.linked {
+ w.connected = false
+ w.noticed = true
+ notice = "WhatsApp is disconnected and does not connect again by itself: " + event.PermanentDisconnectDescription() + ". Restart the daemon to try again."
+ level = slog.LevelError
+ line = "disconnected for good, restart to retry"
+ attrs = append(attrs, "err", whatsappRedact(w.own, event.PermanentDisconnectDescription()))
+ }
+ }
+
+ if w.closed {
+ line = ""