messagetracking.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2022 Tulir Asokan
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "context"
  19. "errors"
  20. "fmt"
  21. "sync"
  22. "go.mau.fi/whatsmeow"
  23. log "maunium.net/go/maulogger/v2"
  24. "maunium.net/go/mautrix"
  25. "maunium.net/go/mautrix/bridge"
  26. "maunium.net/go/mautrix/event"
  27. "maunium.net/go/mautrix/id"
  28. )
  29. var (
  30. errUserNotConnected = errors.New("you are not connected to WhatsApp")
  31. errDifferentUser = errors.New("user is not the recipient of this private chat portal")
  32. errUserNotLoggedIn = errors.New("user is not logged in and chat has no relay bot")
  33. errMNoticeDisabled = errors.New("bridging m.notice messages is disabled")
  34. errUnexpectedParsedContentType = errors.New("unexpected parsed content type")
  35. errInvalidGeoURI = errors.New("invalid `geo:` URI in message")
  36. errUnknownMsgType = errors.New("unknown msgtype")
  37. errMediaDownloadFailed = errors.New("failed to download media")
  38. errMediaDecryptFailed = errors.New("failed to decrypt media")
  39. errMediaConvertFailed = errors.New("failed to convert media")
  40. errMediaWhatsAppUploadFailed = errors.New("failed to upload media to WhatsApp")
  41. errTargetNotFound = errors.New("target event not found")
  42. errReactionDatabaseNotFound = errors.New("reaction database entry not found")
  43. errReactionTargetNotFound = errors.New("reaction target message not found")
  44. errTargetIsFake = errors.New("target is a fake event")
  45. errTargetSentBySomeoneElse = errors.New("target is a fake event")
  46. errBroadcastReactionNotSupported = errors.New("reacting to status messages is not currently supported")
  47. errBroadcastSendDisabled = errors.New("sending status messages is disabled")
  48. errMessageDisconnected = &whatsmeow.DisconnectedError{Action: "message send"}
  49. errMessageRetryDisconnected = &whatsmeow.DisconnectedError{Action: "message send (retry)"}
  50. errMessageTakingLong = errors.New("bridging the message is taking longer than usual")
  51. errTimeoutBeforeHandling = errors.New("message timed out before handling was started")
  52. )
  53. func errorToStatusReason(err error) (reason event.MessageStatusReason, isCertain, canRetry, sendNotice bool) {
  54. switch {
  55. case errors.Is(err, whatsmeow.ErrBroadcastListUnsupported),
  56. errors.Is(err, errUnexpectedParsedContentType),
  57. errors.Is(err, errUnknownMsgType),
  58. errors.Is(err, errInvalidGeoURI),
  59. errors.Is(err, whatsmeow.ErrUnknownServer),
  60. errors.Is(err, whatsmeow.ErrRecipientADJID),
  61. errors.Is(err, errBroadcastReactionNotSupported),
  62. errors.Is(err, errBroadcastSendDisabled):
  63. return event.MessageStatusUnsupported, true, false, true
  64. case errors.Is(err, errTimeoutBeforeHandling):
  65. return event.MessageStatusTooOld, true, true, true
  66. case errors.Is(err, context.DeadlineExceeded):
  67. return event.MessageStatusTooOld, false, true, true
  68. case errors.Is(err, errMessageTakingLong):
  69. // Set can_retry=false here since we'll send another status event allowing the retry later
  70. // Technically retrying when this happens is fine, but we'd just ignore it anyway.
  71. return event.MessageStatusTooOld, false, false, true
  72. case errors.Is(err, errTargetNotFound),
  73. errors.Is(err, errTargetIsFake),
  74. errors.Is(err, errReactionDatabaseNotFound),
  75. errors.Is(err, errReactionTargetNotFound),
  76. errors.Is(err, errTargetSentBySomeoneElse):
  77. return event.MessageStatusGenericError, true, false, false
  78. case errors.Is(err, whatsmeow.ErrNotConnected),
  79. errors.Is(err, errUserNotConnected):
  80. return event.MessageStatusGenericError, true, true, true
  81. case errors.Is(err, errUserNotLoggedIn),
  82. errors.Is(err, errDifferentUser):
  83. return event.MessageStatusGenericError, true, true, false
  84. case errors.Is(err, errMessageDisconnected),
  85. errors.Is(err, errMessageRetryDisconnected):
  86. return event.MessageStatusGenericError, false, true, true
  87. default:
  88. return event.MessageStatusGenericError, false, true, true
  89. }
  90. }
  91. func (portal *Portal) sendErrorMessage(err error, confirmed bool, editID id.EventID) id.EventID {
  92. if !portal.bridge.Config.Bridge.MessageErrorNotices {
  93. return ""
  94. }
  95. certainty := "may not have been"
  96. if confirmed {
  97. certainty = "was not"
  98. }
  99. msg := fmt.Sprintf("\u26a0 Your message %s bridged: %v", certainty, err)
  100. if errors.Is(err, errMessageTakingLong) {
  101. msg = "\u26a0 Bridging your message is taking longer than usual"
  102. }
  103. content := &event.MessageEventContent{
  104. MsgType: event.MsgNotice,
  105. Body: msg,
  106. }
  107. if editID != "" {
  108. content.SetEdit(editID)
  109. }
  110. resp, err := portal.sendMainIntentMessage(content)
  111. if err != nil {
  112. portal.log.Warnfln("Failed to send bridging error message:", err)
  113. return ""
  114. }
  115. return resp.EventID
  116. }
  117. func (portal *Portal) sendStatusEvent(evtID, lastRetry id.EventID, err error) {
  118. if !portal.bridge.Config.Bridge.MessageStatusEvents {
  119. return
  120. }
  121. if lastRetry == evtID {
  122. lastRetry = ""
  123. }
  124. intent := portal.bridge.Bot
  125. if !portal.Encrypted {
  126. // Bridge bot isn't present in unencrypted DMs
  127. intent = portal.MainIntent()
  128. }
  129. content := event.BeeperMessageStatusEventContent{
  130. Network: portal.getBridgeInfoStateKey(),
  131. RelatesTo: event.RelatesTo{
  132. Type: event.RelReference,
  133. EventID: evtID,
  134. },
  135. Success: err == nil,
  136. LastRetry: lastRetry,
  137. }
  138. if !content.Success {
  139. reason, isCertain, canRetry, _ := errorToStatusReason(err)
  140. content.Reason = reason
  141. content.IsCertain = &isCertain
  142. content.CanRetry = &canRetry
  143. content.Error = err.Error()
  144. }
  145. _, err = intent.SendMessageEvent(portal.MXID, event.BeeperMessageStatus, &content)
  146. if err != nil {
  147. portal.log.Warnln("Failed to send message status event:", err)
  148. }
  149. }
  150. func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) {
  151. if portal.bridge.Config.Bridge.DeliveryReceipts {
  152. err := portal.bridge.Bot.MarkRead(portal.MXID, eventID)
  153. if err != nil {
  154. portal.log.Debugfln("Failed to send delivery receipt for %s: %v", eventID, err)
  155. }
  156. }
  157. }
  158. func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part string, ms *metricSender) {
  159. var msgType string
  160. switch evt.Type {
  161. case event.EventMessage:
  162. msgType = "message"
  163. case event.EventReaction:
  164. msgType = "reaction"
  165. case event.EventRedaction:
  166. msgType = "redaction"
  167. default:
  168. msgType = "unknown event"
  169. }
  170. evtDescription := evt.ID.String()
  171. if evt.Type == event.EventRedaction {
  172. evtDescription += fmt.Sprintf(" of %s", evt.Redacts)
  173. }
  174. origEvtID := evt.ID
  175. if retryMeta := evt.Content.AsMessage().MessageSendRetry; retryMeta != nil {
  176. origEvtID = retryMeta.OriginalEventID
  177. }
  178. if err != nil {
  179. level := log.LevelError
  180. if part == "Ignoring" {
  181. level = log.LevelDebug
  182. }
  183. portal.log.Logfln(level, "%s %s %s from %s: %v", part, msgType, evtDescription, evt.Sender, err)
  184. reason, isCertain, _, sendNotice := errorToStatusReason(err)
  185. status := bridge.ReasonToCheckpointStatus(reason)
  186. if errors.Is(err, errMessageTakingLong) {
  187. status = bridge.MsgStatusWillRetry
  188. }
  189. portal.bridge.SendMessageCheckpoint(evt, bridge.MsgStepRemote, err, status, ms.getRetryNum())
  190. if sendNotice {
  191. ms.setNoticeID(portal.sendErrorMessage(err, isCertain, ms.getNoticeID()))
  192. }
  193. portal.sendStatusEvent(origEvtID, evt.ID, err)
  194. } else {
  195. portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription)
  196. portal.sendDeliveryReceipt(evt.ID)
  197. portal.bridge.SendMessageSuccessCheckpoint(evt, bridge.MsgStepRemote, ms.getRetryNum())
  198. portal.sendStatusEvent(origEvtID, evt.ID, nil)
  199. if prevNotice := ms.popNoticeID(); prevNotice != "" {
  200. _, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{
  201. Reason: "error resolved",
  202. })
  203. }
  204. }
  205. }
  206. type metricSender struct {
  207. portal *Portal
  208. previousNotice id.EventID
  209. lock sync.Mutex
  210. completed bool
  211. retryNum int
  212. }
  213. func (ms *metricSender) getRetryNum() int {
  214. if ms != nil {
  215. return ms.retryNum
  216. }
  217. return 0
  218. }
  219. func (ms *metricSender) getNoticeID() id.EventID {
  220. if ms == nil {
  221. return ""
  222. }
  223. return ms.previousNotice
  224. }
  225. func (ms *metricSender) popNoticeID() id.EventID {
  226. if ms == nil {
  227. return ""
  228. }
  229. evtID := ms.previousNotice
  230. ms.previousNotice = ""
  231. return evtID
  232. }
  233. func (ms *metricSender) setNoticeID(evtID id.EventID) {
  234. if ms != nil && ms.previousNotice == "" {
  235. ms.previousNotice = evtID
  236. }
  237. }
  238. func (ms *metricSender) sendMessageMetrics(evt *event.Event, err error, part string, completed bool) {
  239. ms.lock.Lock()
  240. defer ms.lock.Unlock()
  241. if !completed && ms.completed {
  242. return
  243. }
  244. ms.portal.sendMessageMetrics(evt, err, part, ms)
  245. ms.retryNum++
  246. ms.completed = completed
  247. }