presence.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) 2021 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "context"
  9. "fmt"
  10. waBinary "go.mau.fi/whatsmeow/binary"
  11. "go.mau.fi/whatsmeow/types"
  12. "go.mau.fi/whatsmeow/types/events"
  13. )
  14. func (cli *Client) handleChatState(ctx context.Context, node *waBinary.Node) {
  15. source, err := cli.parseMessageSource(node, true)
  16. if err != nil {
  17. cli.Log.Warnf("Failed to parse chat state update: %v", err)
  18. } else if len(node.GetChildren()) != 1 {
  19. cli.Log.Warnf("Failed to parse chat state update: unexpected number of children in element (%d)", len(node.GetChildren()))
  20. } else {
  21. child := node.GetChildren()[0]
  22. presence := types.ChatPresence(child.Tag)
  23. if presence != types.ChatPresenceComposing && presence != types.ChatPresencePaused {
  24. cli.Log.Warnf("Unrecognized chat presence state %s", child.Tag)
  25. }
  26. media := types.ChatPresenceMedia(child.AttrGetter().OptionalString("media"))
  27. cli.dispatchEvent(&events.ChatPresence{
  28. MessageSource: source,
  29. State: presence,
  30. Media: media,
  31. })
  32. }
  33. }
  34. func (cli *Client) handlePresence(ctx context.Context, node *waBinary.Node) {
  35. var evt events.Presence
  36. ag := node.AttrGetter()
  37. evt.From = ag.JID("from")
  38. presenceType := ag.OptionalString("type")
  39. if presenceType == "unavailable" {
  40. evt.Unavailable = true
  41. } else if presenceType != "" {
  42. cli.Log.Debugf("Unrecognized presence type '%s' in presence event from %s", presenceType, evt.From)
  43. }
  44. lastSeen := ag.OptionalString("last")
  45. if lastSeen != "" && lastSeen != "deny" {
  46. evt.LastSeen = ag.UnixTime("last")
  47. }
  48. if !ag.OK() {
  49. cli.Log.Warnf("Error parsing presence event: %+v", ag.Errors)
  50. } else {
  51. cli.dispatchEvent(&evt)
  52. }
  53. }
  54. // SendPresence updates the user's presence status on WhatsApp.
  55. //
  56. // You should call this at least once after connecting so that the server has your pushname.
  57. // Otherwise, other users will see "-" as the name.
  58. func (cli *Client) SendPresence(ctx context.Context, state types.Presence) error {
  59. if cli == nil {
  60. return ErrClientIsNil
  61. } else if len(cli.Store.PushName) == 0 && cli.MessengerConfig == nil {
  62. return ErrNoPushName
  63. }
  64. if state == types.PresenceAvailable {
  65. cli.sendActiveReceipts.CompareAndSwap(0, 1)
  66. } else {
  67. cli.sendActiveReceipts.CompareAndSwap(1, 0)
  68. }
  69. attrs := waBinary.Attrs{
  70. "type": string(state),
  71. }
  72. // PushName not set when using WhatsApp for Messenger E2EE
  73. if cli.MessengerConfig == nil {
  74. attrs["name"] = cli.Store.PushName
  75. }
  76. return cli.sendNode(ctx, waBinary.Node{
  77. Tag: "presence",
  78. Attrs: attrs,
  79. })
  80. }
  81. // SubscribePresence asks the WhatsApp servers to send presence updates of a specific user to this client.
  82. //
  83. // After subscribing to this event, you should start receiving *events.Presence for that user in normal event handlers.
  84. //
  85. // Also, it seems that the WhatsApp servers require you to be online to receive presence status from other users,
  86. // so you should mark yourself as online before trying to use this function:
  87. //
  88. // cli.SendPresence(types.PresenceAvailable)
  89. func (cli *Client) SubscribePresence(ctx context.Context, jid types.JID) error {
  90. if cli == nil {
  91. return ErrClientIsNil
  92. }
  93. privacyToken, err := cli.Store.PrivacyTokens.GetPrivacyToken(ctx, jid)
  94. if err != nil {
  95. return fmt.Errorf("failed to get privacy token: %w", err)
  96. } else if privacyToken == nil {
  97. if cli.ErrorOnSubscribePresenceWithoutToken {
  98. return fmt.Errorf("%w for %v", ErrNoPrivacyToken, jid.ToNonAD())
  99. } else {
  100. cli.Log.Debugf("Trying to subscribe to presence of %s without privacy token", jid)
  101. }
  102. }
  103. req := waBinary.Node{
  104. Tag: "presence",
  105. Attrs: waBinary.Attrs{
  106. "type": "subscribe",
  107. "to": jid,
  108. },
  109. }
  110. if privacyToken != nil {
  111. req.Content = []waBinary.Node{{
  112. Tag: "tctoken",
  113. Content: privacyToken.Token,
  114. }}
  115. }
  116. return cli.sendNode(ctx, req)
  117. }
  118. // SendChatPresence updates the user's typing status in a specific chat.
  119. //
  120. // The media parameter can be set to indicate the user is recording media (like a voice message) rather than typing a text message.
  121. func (cli *Client) SendChatPresence(ctx context.Context, jid types.JID, state types.ChatPresence, media types.ChatPresenceMedia) error {
  122. ownID := cli.getOwnID()
  123. if ownID.IsEmpty() {
  124. return ErrNotLoggedIn
  125. }
  126. content := []waBinary.Node{{Tag: string(state)}}
  127. if state == types.ChatPresenceComposing && len(media) > 0 {
  128. content[0].Attrs = waBinary.Attrs{
  129. "media": string(media),
  130. }
  131. }
  132. return cli.sendNode(ctx, waBinary.Node{
  133. Tag: "chatstate",
  134. Attrs: waBinary.Attrs{
  135. "from": ownID,
  136. "to": jid,
  137. },
  138. Content: content,
  139. })
  140. }