receipt.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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. "time"
  11. "github.com/rs/zerolog"
  12. "go.mau.fi/util/ptr"
  13. waBinary "git.bobomao.top/joey/testwh/binary"
  14. "git.bobomao.top/joey/testwh/types"
  15. "git.bobomao.top/joey/testwh/types/events"
  16. )
  17. func (cli *Client) handleReceipt(ctx context.Context, node *waBinary.Node) {
  18. var cancelled bool
  19. defer cli.maybeDeferredAck(ctx, node)(&cancelled)
  20. receipt, err := cli.parseReceipt(node)
  21. if err != nil {
  22. cli.Log.Warnf("Failed to parse receipt: %v", err)
  23. } else if receipt != nil {
  24. if receipt.Type == types.ReceiptTypeRetry {
  25. go func() {
  26. err := cli.handleRetryReceipt(ctx, receipt, node)
  27. if err != nil {
  28. cli.Log.Errorf("Failed to handle retry receipt for %s/%s from %s: %v", receipt.Chat, receipt.MessageIDs[0], receipt.Sender, err)
  29. }
  30. }()
  31. }
  32. cancelled = cli.dispatchEvent(receipt)
  33. }
  34. }
  35. func (cli *Client) handleGroupedReceipt(partialReceipt events.Receipt, participants *waBinary.Node) {
  36. pag := participants.AttrGetter()
  37. partialReceipt.MessageIDs = []types.MessageID{pag.String("key")}
  38. for _, child := range participants.GetChildren() {
  39. if child.Tag != "user" {
  40. cli.Log.Warnf("Unexpected node in grouped receipt participants: %s", child.XMLString())
  41. continue
  42. }
  43. ag := child.AttrGetter()
  44. receipt := partialReceipt
  45. receipt.Timestamp = ag.UnixTime("t")
  46. receipt.MessageSource.Sender = ag.JID("jid")
  47. if !ag.OK() {
  48. cli.Log.Warnf("Failed to parse user node %s in grouped receipt: %v", child.XMLString(), ag.Error())
  49. continue
  50. }
  51. cli.dispatchEvent(&receipt)
  52. }
  53. }
  54. func (cli *Client) parseReceipt(node *waBinary.Node) (*events.Receipt, error) {
  55. ag := node.AttrGetter()
  56. source, err := cli.parseMessageSource(node, false)
  57. if err != nil {
  58. return nil, err
  59. }
  60. receipt := events.Receipt{
  61. MessageSource: source,
  62. Timestamp: ag.UnixTime("t"),
  63. Type: types.ReceiptType(ag.OptionalString("type")),
  64. MessageSender: ag.OptionalJIDOrEmpty("recipient"),
  65. }
  66. if source.IsGroup && source.Sender.IsEmpty() {
  67. participantTags := node.GetChildrenByTag("participants")
  68. if len(participantTags) == 0 {
  69. return nil, &ElementMissingError{Tag: "participants", In: "grouped receipt"}
  70. }
  71. for _, pcp := range participantTags {
  72. cli.handleGroupedReceipt(receipt, &pcp)
  73. }
  74. return nil, nil
  75. }
  76. mainMessageID := ag.String("id")
  77. if !ag.OK() {
  78. return nil, fmt.Errorf("failed to parse read receipt attrs: %+v", ag.Errors)
  79. }
  80. receiptChildren := node.GetChildren()
  81. if len(receiptChildren) == 1 && receiptChildren[0].Tag == "list" {
  82. listChildren := receiptChildren[0].GetChildren()
  83. receipt.MessageIDs = make([]string, 1, len(listChildren)+1)
  84. receipt.MessageIDs[0] = mainMessageID
  85. for _, item := range listChildren {
  86. if id, ok := item.Attrs["id"].(string); ok && item.Tag == "item" {
  87. receipt.MessageIDs = append(receipt.MessageIDs, id)
  88. }
  89. }
  90. } else {
  91. receipt.MessageIDs = []types.MessageID{mainMessageID}
  92. }
  93. return &receipt, nil
  94. }
  95. func (cli *Client) backgroundIfAsyncAck(fn func()) {
  96. if cli.SynchronousAck {
  97. fn()
  98. } else {
  99. go fn()
  100. }
  101. }
  102. func (cli *Client) maybeDeferredAck(ctx context.Context, node *waBinary.Node) func(cancelled ...*bool) {
  103. if cli.SynchronousAck {
  104. return func(cancelled ...*bool) {
  105. isCancelled := len(cancelled) > 0 && ptr.Val(cancelled[0])
  106. if ctx.Err() != nil || isCancelled {
  107. zerolog.Ctx(ctx).Debug().
  108. AnErr("ctx_err", ctx.Err()).
  109. Bool("cancelled", isCancelled).
  110. Str("node_tag", node.Tag).
  111. Msg("Not sending ack for node")
  112. return
  113. }
  114. cli.sendAck(ctx, node, 0)
  115. }
  116. } else {
  117. go cli.sendAck(ctx, node, 0)
  118. return func(...*bool) {}
  119. }
  120. }
  121. const (
  122. NackParsingError = 487
  123. NackUnrecognizedStanza = 488
  124. NackUnrecognizedStanzaClass = 489
  125. NackUnrecognizedStanzaType = 490
  126. NackInvalidProtobuf = 491
  127. NackInvalidHostedCompanionStanza = 493
  128. NackMissingMessageSecret = 495
  129. NackSignalErrorOldCounter = 496
  130. NackMessageDeletedOnPeer = 499
  131. NackUnhandledError = 500
  132. NackUnsupportedAdminRevoke = 550
  133. NackUnsupportedLIDGroup = 551
  134. NackDBOperationFailed = 552
  135. )
  136. func (cli *Client) sendAck(ctx context.Context, node *waBinary.Node, error int) {
  137. attrs := waBinary.Attrs{
  138. "class": node.Tag,
  139. "id": node.Attrs["id"],
  140. }
  141. attrs["to"] = node.Attrs["from"]
  142. if participant, ok := node.Attrs["participant"]; ok {
  143. attrs["participant"] = participant
  144. }
  145. if recipient, ok := node.Attrs["recipient"]; ok {
  146. attrs["recipient"] = recipient
  147. // TODO this hack probably needs to be removed at some point
  148. recipientJID, ok := recipient.(types.JID)
  149. if ok && recipientJID.Server == types.BotServer && node.Tag == "message" {
  150. altRecipient, ok := types.BotJIDMap[recipientJID]
  151. if ok {
  152. attrs["recipient"] = altRecipient
  153. }
  154. }
  155. }
  156. if receiptType, ok := node.Attrs["type"]; node.Tag != "message" && ok {
  157. attrs["type"] = receiptType
  158. }
  159. if error != 0 {
  160. attrs["error"] = error
  161. }
  162. err := cli.sendNode(ctx, waBinary.Node{
  163. Tag: "ack",
  164. Attrs: attrs,
  165. })
  166. if err != nil {
  167. cli.Log.Warnf("Failed to send acknowledgement for %s %s: %v", node.Tag, node.Attrs["id"], err)
  168. }
  169. }
  170. // MarkRead sends a read receipt for the given message IDs including the given timestamp as the read at time.
  171. //
  172. // The first JID parameter (chat) must always be set to the chat ID (user ID in DMs and group ID in group chats).
  173. // The second JID parameter (sender) must be set in group chats and must be the user ID who sent the message.
  174. //
  175. // You can mark multiple messages as read at the same time, but only if the messages were sent by the same user.
  176. // To mark messages by different users as read, you must call MarkRead multiple times (once for each user).
  177. //
  178. // To mark a voice message as played, specify types.ReceiptTypePlayed as the last parameter.
  179. // Providing more than one receipt type will panic: the parameter is only a vararg for backwards compatibility.
  180. func (cli *Client) MarkRead(ctx context.Context, ids []types.MessageID, timestamp time.Time, chat, sender types.JID, receiptTypeExtra ...types.ReceiptType) error {
  181. if len(ids) == 0 {
  182. return fmt.Errorf("no message IDs specified")
  183. }
  184. receiptType := types.ReceiptTypeRead
  185. if len(receiptTypeExtra) == 1 {
  186. receiptType = receiptTypeExtra[0]
  187. } else if len(receiptTypeExtra) > 1 {
  188. panic(fmt.Errorf("too many receipt types specified"))
  189. }
  190. node := waBinary.Node{
  191. Tag: "receipt",
  192. Attrs: waBinary.Attrs{
  193. "id": ids[0],
  194. "type": string(receiptType),
  195. "to": chat,
  196. "t": timestamp.Unix(),
  197. },
  198. }
  199. if chat.Server == types.NewsletterServer || cli.GetPrivacySettings(ctx).ReadReceipts == types.PrivacySettingNone {
  200. switch receiptType {
  201. case types.ReceiptTypeRead:
  202. node.Attrs["type"] = string(types.ReceiptTypeReadSelf)
  203. // TODO change played to played-self?
  204. }
  205. }
  206. if !sender.IsEmpty() && chat.Server != types.DefaultUserServer && chat.Server != types.HiddenUserServer && chat.Server != types.MessengerServer {
  207. node.Attrs["participant"] = sender.ToNonAD()
  208. }
  209. if len(ids) > 1 {
  210. children := make([]waBinary.Node, len(ids)-1)
  211. for i := 1; i < len(ids); i++ {
  212. children[i-1].Tag = "item"
  213. children[i-1].Attrs = waBinary.Attrs{"id": ids[i]}
  214. }
  215. node.Content = []waBinary.Node{{
  216. Tag: "list",
  217. Content: children,
  218. }}
  219. }
  220. return cli.sendNode(ctx, node)
  221. }
  222. // SetForceActiveDeliveryReceipts will force the client to send normal delivery
  223. // receipts (which will show up as the two gray ticks on WhatsApp), even if the
  224. // client isn't marked as online.
  225. //
  226. // By default, clients that haven't been marked as online will send delivery
  227. // receipts with type="inactive", which is transmitted to the sender, but not
  228. // rendered in the official WhatsApp apps. This is consistent with how WhatsApp
  229. // web works when it's not in the foreground.
  230. //
  231. // To mark the client as online, use
  232. //
  233. // cli.SendPresence(types.PresenceAvailable)
  234. //
  235. // Note that if you turn this off (i.e. call SetForceActiveDeliveryReceipts(false)),
  236. // receipts will act like the client is offline until SendPresence is called again.
  237. func (cli *Client) SetForceActiveDeliveryReceipts(active bool) {
  238. if cli == nil {
  239. return
  240. }
  241. if active {
  242. cli.sendActiveReceipts.Store(2)
  243. } else {
  244. cli.sendActiveReceipts.Store(0)
  245. }
  246. }
  247. func buildBaseReceipt(id string, node *waBinary.Node) waBinary.Attrs {
  248. attrs := waBinary.Attrs{
  249. "id": id,
  250. "to": node.Attrs["from"],
  251. }
  252. if recipient, ok := node.Attrs["recipient"]; ok {
  253. attrs["recipient"] = recipient
  254. }
  255. if participant, ok := node.Attrs["participant"]; ok {
  256. attrs["participant"] = participant
  257. }
  258. return attrs
  259. }
  260. func (cli *Client) sendMessageReceipt(ctx context.Context, info *types.MessageInfo, node *waBinary.Node) {
  261. attrs := buildBaseReceipt(info.ID, node)
  262. if info.IsFromMe {
  263. attrs["type"] = string(types.ReceiptTypeSender)
  264. if info.Type == "peer_msg" {
  265. attrs["type"] = string(types.ReceiptTypePeerMsg)
  266. }
  267. } else if cli.sendActiveReceipts.Load() == 0 {
  268. attrs["type"] = string(types.ReceiptTypeInactive)
  269. }
  270. err := cli.sendNode(ctx, waBinary.Node{
  271. Tag: "receipt",
  272. Attrs: attrs,
  273. })
  274. if err != nil {
  275. cli.Log.Warnf("Failed to send receipt for %s: %v", info.ID, err)
  276. }
  277. }