diff --git a/API.md b/API.md index c6a2216d..47478557 100644 --- a/API.md +++ b/API.md @@ -1007,6 +1007,37 @@ curl -X POST -H 'Token: 1234ABCD' -H 'Content-Type: application/json' --data '{" --- +## Subscribe to Contact Presence + +Subscribes to a contact's presence updates (online/offline and last seen). After +subscribing, your configured webhook receives `Presence` events for that contact. You +should be online yourself to receive presence (wuzapi sends an available presence on +connect). Whether `last_seen` is available depends on the contact's privacy settings. + +endpoint: _/user/presence/subscribe_ + +method: **POST** + +``` +curl -X POST -H 'Token: 1234ABCD' -H 'Content-Type: application/json' --data '{"Phone":"5491155554444"}' http://localhost:8080/user/presence/subscribe +``` + +The resulting webhook `Presence` event looks like: + +```json +{ + "type": "Presence", + "state": "offline", + "from": "5491155554444@s.whatsapp.net", + "last_seen": 1750000000 +} +``` + +`last_seen` is a Unix timestamp, present only when the contact is offline and shares +their last seen. When the contact is online, only `type`, `from` and `state` are sent. + +--- + ## Mark message(s) as read Indicates that one or more messages were read. Id is an array of messages Ids. diff --git a/handlers.go b/handlers.go index 8df3a210..ef64235b 100644 --- a/handlers.go +++ b/handlers.go @@ -3439,6 +3439,64 @@ func (s *server) SendPresence() http.HandlerFunc { } } +// Subscribes to a contact's presence (online/offline + last seen). +// After subscribing, the configured webhook receives "Presence" events for that +// contact (with state and, when offline, a last_seen unix timestamp). Requires the +// session to be available/online (wuzapi sends available presence on connect). +func (s *server) SubscribePresence() http.HandlerFunc { + + type subscribePresenceStruct struct { + Phone string + } + + return func(w http.ResponseWriter, r *http.Request) { + + txtid := r.Context().Value("userinfo").(Values).Get("Id") + + if clientManager.GetWhatsmeowClient(txtid) == nil { + s.Respond(w, r, http.StatusInternalServerError, errors.New("no session")) + return + } + + decoder := json.NewDecoder(r.Body) + var t subscribePresenceStruct + err := decoder.Decode(&t) + if err != nil { + s.Respond(w, r, http.StatusBadRequest, errors.New("could not decode Payload")) + return + } + + if len(t.Phone) < 1 { + s.Respond(w, r, http.StatusBadRequest, errors.New("missing Phone in Payload")) + return + } + + jid, ok := parseJID(t.Phone) + if !ok { + s.Respond(w, r, http.StatusBadRequest, errors.New("could not parse Phone")) + return + } + + err = clientManager.GetWhatsmeowClient(txtid).SubscribePresence(r.Context(), jid) + if err != nil { + s.Respond(w, r, http.StatusInternalServerError, errors.New("failure subscribing to presence")) + return + } + + log.Info().Str("jid", jid.String()).Msg("Subscribed to presence") + + response := map[string]interface{}{"Details": "Presence subscription requested successfully"} + responseJson, err := json.Marshal(response) + if err != nil { + s.Respond(w, r, http.StatusInternalServerError, err) + } else { + s.Respond(w, r, http.StatusOK, string(responseJson)) + } + return + + } +} + // Gets avatar info for user func (s *server) GetAvatar() http.HandlerFunc { diff --git a/routes.go b/routes.go index 21b40251..ddb53655 100644 --- a/routes.go +++ b/routes.go @@ -127,6 +127,7 @@ func (s *server) routes() { s.router.Handle("/call/reject", c.Then(s.RejectCall())).Methods("POST") s.router.Handle("/user/presence", c.Then(s.SendPresence())).Methods("POST") + s.router.Handle("/user/presence/subscribe", c.Then(s.SubscribePresence())).Methods("POST") s.router.Handle("/user/info", c.Then(s.GetUser())).Methods("POST") s.router.Handle("/user/check", c.Then(s.CheckUser())).Methods("POST") s.router.Handle("/user/avatar", c.Then(s.GetAvatar())).Methods("POST") diff --git a/wmiau.go b/wmiau.go index 4e638203..624c7b34 100644 --- a/wmiau.go +++ b/wmiau.go @@ -1158,11 +1158,13 @@ func (mycli *MyClient) myEventHandler(rawEvt interface{}) { case *events.Presence: postmap["type"] = "Presence" dowebhook = 1 + postmap["from"] = evt.From.String() if evt.Unavailable { postmap["state"] = "offline" if evt.LastSeen.IsZero() { log.Info().Str("from", evt.From.String()).Msg("User is now offline") } else { + postmap["last_seen"] = evt.LastSeen.Unix() log.Info().Str("from", evt.From.String()).Str("lastSeen", fmt.Sprintf("%v", evt.LastSeen)).Msg("User is now offline") } } else {