notification.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. "encoding/json"
  10. "errors"
  11. "slices"
  12. "google.golang.org/protobuf/proto"
  13. "git.bobomao.top/joey/testwh/appstate"
  14. waBinary "git.bobomao.top/joey/testwh/binary"
  15. "git.bobomao.top/joey/testwh/proto/waE2E"
  16. "git.bobomao.top/joey/testwh/store"
  17. "git.bobomao.top/joey/testwh/types"
  18. "git.bobomao.top/joey/testwh/types/events"
  19. )
  20. func (cli *Client) handleEncryptNotification(ctx context.Context, node *waBinary.Node) {
  21. from := node.AttrGetter().JID("from")
  22. if from == types.ServerJID {
  23. count := node.GetChildByTag("count")
  24. ag := count.AttrGetter()
  25. otksLeft := ag.Int("value")
  26. if !ag.OK() {
  27. cli.Log.Warnf("Didn't get number of OTKs left in encryption notification %s", node.XMLString())
  28. return
  29. }
  30. cli.Log.Infof("Got prekey count from server: %s", node.XMLString())
  31. if otksLeft < MinPreKeyCount {
  32. cli.uploadPreKeys(ctx, false)
  33. }
  34. } else if _, ok := node.GetOptionalChildByTag("identity"); ok {
  35. cli.Log.Debugf("Got identity change for %s: %s, deleting all identities/sessions for that number", from, node.XMLString())
  36. err := cli.Store.Identities.DeleteAllIdentities(ctx, from.User)
  37. if err != nil {
  38. cli.Log.Warnf("Failed to delete all identities of %s from store after identity change: %v", from, err)
  39. }
  40. err = cli.Store.Sessions.DeleteAllSessions(ctx, from.User)
  41. if err != nil {
  42. cli.Log.Warnf("Failed to delete all sessions of %s from store after identity change: %v", from, err)
  43. }
  44. ts := node.AttrGetter().UnixTime("t")
  45. cli.dispatchEvent(&events.IdentityChange{JID: from, Timestamp: ts})
  46. } else {
  47. cli.Log.Debugf("Got unknown encryption notification from server: %s", node.XMLString())
  48. }
  49. }
  50. func (cli *Client) handleAppStateNotification(ctx context.Context, node *waBinary.Node) {
  51. for _, collection := range node.GetChildrenByTag("collection") {
  52. ag := collection.AttrGetter()
  53. name := appstate.WAPatchName(ag.String("name"))
  54. version := ag.Uint64("version")
  55. cli.Log.Debugf("Got server sync notification that app state %s has updated to version %d", name, version)
  56. err := cli.FetchAppState(ctx, name, false, false)
  57. if errors.Is(err, ErrIQDisconnected) || errors.Is(err, ErrNotConnected) {
  58. // There are some app state changes right before a remote logout, so stop syncing if we're disconnected.
  59. cli.Log.Debugf("Failed to sync app state after notification: %v, not trying to sync other states", err)
  60. return
  61. } else if err != nil {
  62. cli.Log.Errorf("Failed to sync app state after notification: %v", err)
  63. }
  64. }
  65. }
  66. func (cli *Client) handlePictureNotification(ctx context.Context, node *waBinary.Node) {
  67. ts := node.AttrGetter().UnixTime("t")
  68. for _, child := range node.GetChildren() {
  69. ag := child.AttrGetter()
  70. var evt events.Picture
  71. evt.Timestamp = ts
  72. evt.JID = ag.JID("jid")
  73. evt.Author = ag.OptionalJIDOrEmpty("author")
  74. if child.Tag == "delete" {
  75. evt.Remove = true
  76. } else if child.Tag == "add" {
  77. evt.PictureID = ag.String("id")
  78. } else if child.Tag == "set" {
  79. // TODO sometimes there's a hash and no ID?
  80. evt.PictureID = ag.String("id")
  81. } else {
  82. continue
  83. }
  84. if !ag.OK() {
  85. cli.Log.Debugf("Ignoring picture change notification with unexpected attributes: %v", ag.Error())
  86. continue
  87. }
  88. cli.dispatchEvent(&evt)
  89. }
  90. }
  91. func (cli *Client) handleDeviceNotification(ctx context.Context, node *waBinary.Node) {
  92. cli.userDevicesCacheLock.Lock()
  93. defer cli.userDevicesCacheLock.Unlock()
  94. ag := node.AttrGetter()
  95. from := ag.JID("from")
  96. fromLID := ag.OptionalJID("lid")
  97. if fromLID != nil {
  98. cli.StoreLIDPNMapping(ctx, *fromLID, from)
  99. }
  100. cached, ok := cli.userDevicesCache[from]
  101. if !ok {
  102. cli.Log.Debugf("No device list cached for %s, ignoring device list notification", from)
  103. return
  104. }
  105. var cachedLID deviceCache
  106. var cachedLIDHash string
  107. if fromLID != nil {
  108. cachedLID = cli.userDevicesCache[*fromLID]
  109. cachedLIDHash = participantListHashV2(cachedLID.devices)
  110. }
  111. cachedParticipantHash := participantListHashV2(cached.devices)
  112. for _, child := range node.GetChildren() {
  113. cag := child.AttrGetter()
  114. deviceHash := cag.String("device_hash")
  115. deviceLIDHash := cag.OptionalString("device_lid_hash")
  116. deviceChild, _ := child.GetOptionalChildByTag("device")
  117. changedDeviceJID := deviceChild.AttrGetter().JID("jid")
  118. changedDeviceLID := deviceChild.AttrGetter().OptionalJID("lid")
  119. switch child.Tag {
  120. case "add":
  121. cached.devices = append(cached.devices, changedDeviceJID)
  122. if changedDeviceLID != nil {
  123. cachedLID.devices = append(cachedLID.devices, *changedDeviceLID)
  124. }
  125. case "remove":
  126. cached.devices = slices.DeleteFunc(cached.devices, func(existing types.JID) bool {
  127. return existing == changedDeviceJID
  128. })
  129. if changedDeviceLID != nil {
  130. cachedLID.devices = slices.DeleteFunc(cachedLID.devices, func(existing types.JID) bool {
  131. return existing == *changedDeviceLID
  132. })
  133. }
  134. case "update":
  135. // Exact meaning of "update" is unknown, clear device list cache to be safe
  136. cli.Log.Debugf("%s's device list updated, dropping cached devices", from)
  137. delete(cli.userDevicesCache, from)
  138. continue
  139. default:
  140. cli.Log.Debugf("Unknown device list change tag %s", child.Tag)
  141. continue
  142. }
  143. newParticipantHash := participantListHashV2(cached.devices)
  144. if newParticipantHash == deviceHash {
  145. cli.Log.Debugf("%s's device list hash changed from %s to %s (%s). New hash matches", from, cachedParticipantHash, deviceHash, child.Tag)
  146. cli.userDevicesCache[from] = cached
  147. } else {
  148. cli.Log.Warnf("%s's device list hash changed from %s to %s (%s). New hash doesn't match (%s)", from, cachedParticipantHash, deviceHash, child.Tag, newParticipantHash)
  149. delete(cli.userDevicesCache, from)
  150. }
  151. if fromLID != nil && changedDeviceLID != nil && deviceLIDHash != "" {
  152. newLIDParticipantHash := participantListHashV2(cachedLID.devices)
  153. if newLIDParticipantHash == deviceLIDHash {
  154. cli.Log.Debugf("%s's device list hash changed from %s to %s (%s). New hash matches", fromLID, cachedLIDHash, deviceLIDHash, child.Tag)
  155. cli.userDevicesCache[*fromLID] = cachedLID
  156. } else {
  157. cli.Log.Warnf("%s's device list hash changed from %s to %s (%s). New hash doesn't match (%s)", fromLID, cachedLIDHash, deviceLIDHash, child.Tag, newLIDParticipantHash)
  158. delete(cli.userDevicesCache, *fromLID)
  159. }
  160. }
  161. }
  162. }
  163. func (cli *Client) handleFBDeviceNotification(ctx context.Context, node *waBinary.Node) {
  164. cli.userDevicesCacheLock.Lock()
  165. defer cli.userDevicesCacheLock.Unlock()
  166. jid := node.AttrGetter().JID("from")
  167. userDevices := parseFBDeviceList(jid, node.GetChildByTag("devices"))
  168. cli.userDevicesCache[jid] = userDevices
  169. }
  170. func (cli *Client) handleOwnDevicesNotification(ctx context.Context, node *waBinary.Node) {
  171. cli.userDevicesCacheLock.Lock()
  172. defer cli.userDevicesCacheLock.Unlock()
  173. ownID := cli.getOwnID().ToNonAD()
  174. if ownID.IsEmpty() {
  175. cli.Log.Debugf("Ignoring own device change notification, session was deleted")
  176. return
  177. }
  178. cached, ok := cli.userDevicesCache[ownID]
  179. if !ok {
  180. cli.Log.Debugf("Ignoring own device change notification, device list not cached")
  181. return
  182. }
  183. oldHash := participantListHashV2(cached.devices)
  184. expectedNewHash := node.AttrGetter().String("dhash")
  185. var newDeviceList []types.JID
  186. for _, child := range node.GetChildren() {
  187. jid := child.AttrGetter().JID("jid")
  188. if child.Tag == "device" && !jid.IsEmpty() {
  189. newDeviceList = append(newDeviceList, jid)
  190. }
  191. }
  192. newHash := participantListHashV2(newDeviceList)
  193. if newHash != expectedNewHash {
  194. cli.Log.Debugf("Received own device list change notification %s -> %s, but expected hash was %s", oldHash, newHash, expectedNewHash)
  195. delete(cli.userDevicesCache, ownID)
  196. } else {
  197. cli.Log.Debugf("Received own device list change notification %s -> %s", oldHash, newHash)
  198. cli.userDevicesCache[ownID] = deviceCache{devices: newDeviceList, dhash: expectedNewHash}
  199. }
  200. }
  201. func (cli *Client) handleBlocklist(ctx context.Context, node *waBinary.Node) {
  202. ag := node.AttrGetter()
  203. evt := events.Blocklist{
  204. Action: events.BlocklistAction(ag.OptionalString("action")),
  205. DHash: ag.String("dhash"),
  206. PrevDHash: ag.OptionalString("prev_dhash"),
  207. }
  208. for _, child := range node.GetChildren() {
  209. ag := child.AttrGetter()
  210. change := events.BlocklistChange{
  211. JID: ag.JID("jid"),
  212. Action: events.BlocklistChangeAction(ag.String("action")),
  213. }
  214. if !ag.OK() {
  215. cli.Log.Warnf("Unexpected data in blocklist event child %v: %v", child.XMLString(), ag.Error())
  216. continue
  217. }
  218. evt.Changes = append(evt.Changes, change)
  219. }
  220. cli.dispatchEvent(&evt)
  221. }
  222. func (cli *Client) handleAccountSyncNotification(ctx context.Context, node *waBinary.Node) {
  223. for _, child := range node.GetChildren() {
  224. switch child.Tag {
  225. case "privacy":
  226. cli.handlePrivacySettingsNotification(ctx, &child)
  227. case "devices":
  228. cli.handleOwnDevicesNotification(ctx, &child)
  229. case "picture":
  230. cli.dispatchEvent(&events.Picture{
  231. Timestamp: node.AttrGetter().UnixTime("t"),
  232. JID: cli.getOwnID().ToNonAD(),
  233. })
  234. case "blocklist":
  235. cli.handleBlocklist(ctx, &child)
  236. default:
  237. cli.Log.Debugf("Unhandled account sync item %s", child.Tag)
  238. }
  239. }
  240. }
  241. func (cli *Client) handlePrivacyTokenNotification(ctx context.Context, node *waBinary.Node) {
  242. ownJID := cli.getOwnID().ToNonAD()
  243. ownLID := cli.getOwnLID().ToNonAD()
  244. if ownJID.IsEmpty() {
  245. cli.Log.Debugf("Ignoring privacy token notification, session was deleted")
  246. return
  247. }
  248. tokens := node.GetChildByTag("tokens")
  249. if tokens.Tag != "tokens" {
  250. cli.Log.Warnf("privacy_token notification didn't contain <tokens> tag")
  251. return
  252. }
  253. parentAG := node.AttrGetter()
  254. sender := parentAG.JID("from")
  255. if !parentAG.OK() {
  256. cli.Log.Warnf("privacy_token notification didn't have a sender (%v)", parentAG.Error())
  257. return
  258. }
  259. for _, child := range tokens.GetChildren() {
  260. ag := child.AttrGetter()
  261. if child.Tag != "token" {
  262. cli.Log.Warnf("privacy_token notification contained unexpected <%s> tag", child.Tag)
  263. } else if targetUser := ag.JID("jid"); targetUser != ownLID && targetUser != ownJID {
  264. // Don't log about own privacy tokens for other users
  265. if sender != ownJID && sender != ownLID {
  266. cli.Log.Warnf("privacy_token notification contained token for different user %s", targetUser)
  267. }
  268. } else if tokenType := ag.String("type"); tokenType != "trusted_contact" {
  269. cli.Log.Warnf("privacy_token notification contained unexpected token type %s", tokenType)
  270. } else if token, ok := child.Content.([]byte); !ok {
  271. cli.Log.Warnf("privacy_token notification contained non-binary token")
  272. } else {
  273. timestamp := ag.UnixTime("t")
  274. if !ag.OK() {
  275. cli.Log.Warnf("privacy_token notification is missing some fields: %v", ag.Error())
  276. }
  277. err := cli.Store.PrivacyTokens.PutPrivacyTokens(ctx, store.PrivacyToken{
  278. User: sender,
  279. Token: token,
  280. Timestamp: timestamp,
  281. })
  282. if err != nil {
  283. cli.Log.Errorf("Failed to save privacy token from %s: %v", sender, err)
  284. } else {
  285. cli.Log.Debugf("Stored privacy token from %s (ts: %v)", sender, timestamp)
  286. }
  287. }
  288. }
  289. }
  290. func (cli *Client) parseNewsletterMessages(node *waBinary.Node) []*types.NewsletterMessage {
  291. children := node.GetChildren()
  292. output := make([]*types.NewsletterMessage, 0, len(children))
  293. for _, child := range children {
  294. if child.Tag != "message" {
  295. continue
  296. }
  297. ag := child.AttrGetter()
  298. msg := types.NewsletterMessage{
  299. MessageServerID: ag.Int("server_id"),
  300. MessageID: ag.String("id"),
  301. Type: ag.String("type"),
  302. Timestamp: ag.UnixTime("t"),
  303. ViewsCount: 0,
  304. ReactionCounts: nil,
  305. }
  306. for _, subchild := range child.GetChildren() {
  307. switch subchild.Tag {
  308. case "plaintext":
  309. byteContent, ok := subchild.Content.([]byte)
  310. if ok {
  311. msg.Message = new(waE2E.Message)
  312. err := proto.Unmarshal(byteContent, msg.Message)
  313. if err != nil {
  314. cli.Log.Warnf("Failed to unmarshal newsletter message: %v", err)
  315. msg.Message = nil
  316. }
  317. }
  318. case "views_count":
  319. msg.ViewsCount = subchild.AttrGetter().Int("count")
  320. case "reactions":
  321. msg.ReactionCounts = make(map[string]int)
  322. for _, reaction := range subchild.GetChildren() {
  323. rag := reaction.AttrGetter()
  324. msg.ReactionCounts[rag.String("code")] = rag.Int("count")
  325. }
  326. }
  327. }
  328. output = append(output, &msg)
  329. }
  330. return output
  331. }
  332. func (cli *Client) handleNewsletterNotification(ctx context.Context, node *waBinary.Node) {
  333. ag := node.AttrGetter()
  334. liveUpdates := node.GetChildByTag("live_updates")
  335. cli.dispatchEvent(&events.NewsletterLiveUpdate{
  336. JID: ag.JID("from"),
  337. Time: ag.UnixTime("t"),
  338. Messages: cli.parseNewsletterMessages(&liveUpdates),
  339. })
  340. }
  341. type newsLetterEventWrapper struct {
  342. Data newsletterEvent `json:"data"`
  343. }
  344. type newsletterEvent struct {
  345. Join *events.NewsletterJoin `json:"xwa2_notify_newsletter_on_join"`
  346. Leave *events.NewsletterLeave `json:"xwa2_notify_newsletter_on_leave"`
  347. MuteChange *events.NewsletterMuteChange `json:"xwa2_notify_newsletter_on_mute_change"`
  348. // _on_admin_metadata_update -> id, thread_metadata, messages
  349. // _on_metadata_update
  350. // _on_state_change -> id, is_requestor, state
  351. }
  352. func (cli *Client) handleMexNotification(ctx context.Context, node *waBinary.Node) {
  353. for _, child := range node.GetChildren() {
  354. if child.Tag != "update" {
  355. continue
  356. }
  357. childData, ok := child.Content.([]byte)
  358. if !ok {
  359. continue
  360. }
  361. var wrapper newsLetterEventWrapper
  362. err := json.Unmarshal(childData, &wrapper)
  363. if err != nil {
  364. cli.Log.Errorf("Failed to unmarshal JSON in mex event: %v", err)
  365. continue
  366. }
  367. if wrapper.Data.Join != nil {
  368. cli.dispatchEvent(wrapper.Data.Join)
  369. } else if wrapper.Data.Leave != nil {
  370. cli.dispatchEvent(wrapper.Data.Leave)
  371. } else if wrapper.Data.MuteChange != nil {
  372. cli.dispatchEvent(wrapper.Data.MuteChange)
  373. }
  374. }
  375. }
  376. func (cli *Client) handleStatusNotification(ctx context.Context, node *waBinary.Node) {
  377. ag := node.AttrGetter()
  378. child, found := node.GetOptionalChildByTag("set")
  379. if !found {
  380. cli.Log.Debugf("Status notifcation did not contain child with tag 'set'")
  381. return
  382. }
  383. status, ok := child.Content.([]byte)
  384. if !ok {
  385. cli.Log.Warnf("Set status notification has unexpected content (%T)", child.Content)
  386. return
  387. }
  388. cli.dispatchEvent(&events.UserAbout{
  389. JID: ag.JID("from"),
  390. Timestamp: ag.UnixTime("t"),
  391. Status: string(status),
  392. })
  393. }
  394. func (cli *Client) handleNotification(ctx context.Context, node *waBinary.Node) {
  395. ag := node.AttrGetter()
  396. notifType := ag.String("type")
  397. if !ag.OK() {
  398. return
  399. }
  400. var cancelled bool
  401. defer cli.maybeDeferredAck(ctx, node)(&cancelled)
  402. switch notifType {
  403. case "encrypt":
  404. go cli.handleEncryptNotification(ctx, node)
  405. case "server_sync":
  406. go cli.handleAppStateNotification(ctx, node)
  407. case "account_sync":
  408. go cli.handleAccountSyncNotification(ctx, node)
  409. case "devices":
  410. cli.handleDeviceNotification(ctx, node)
  411. case "fbid:devices":
  412. cli.handleFBDeviceNotification(ctx, node)
  413. case "w:gp2":
  414. evt, lidPairs, redactedPhones, err := cli.parseGroupNotification(node)
  415. if err != nil {
  416. cli.Log.Errorf("Failed to parse group notification: %v", err)
  417. } else {
  418. err = cli.Store.LIDs.PutManyLIDMappings(ctx, lidPairs)
  419. if err != nil {
  420. cli.Log.Errorf("Failed to store LID mappings from group notification: %v", err)
  421. }
  422. err = cli.Store.Contacts.PutManyRedactedPhones(ctx, redactedPhones)
  423. if err != nil {
  424. cli.Log.Warnf("Failed to store redacted phones from group notification: %v", err)
  425. }
  426. cancelled = cli.dispatchEvent(evt)
  427. }
  428. case "picture":
  429. cli.handlePictureNotification(ctx, node)
  430. case "mediaretry":
  431. cli.handleMediaRetryNotification(ctx, node)
  432. case "privacy_token":
  433. cli.handlePrivacyTokenNotification(ctx, node)
  434. case "link_code_companion_reg":
  435. go cli.tryHandleCodePairNotification(ctx, node)
  436. case "newsletter":
  437. cli.handleNewsletterNotification(ctx, node)
  438. case "mex":
  439. cli.handleMexNotification(ctx, node)
  440. case "status":
  441. cli.handleStatusNotification(ctx, node)
  442. // Other types: business, disappearing_mode, server, status, pay, psa
  443. default:
  444. cli.Log.Debugf("Unhandled notification with type %s", notifType)
  445. }
  446. }