user.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  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/base64"
  10. "errors"
  11. "fmt"
  12. "slices"
  13. "strings"
  14. "google.golang.org/protobuf/proto"
  15. waBinary "go.mau.fi/whatsmeow/binary"
  16. "go.mau.fi/whatsmeow/proto/waHistorySync"
  17. "go.mau.fi/whatsmeow/proto/waVnameCert"
  18. "go.mau.fi/whatsmeow/store"
  19. "go.mau.fi/whatsmeow/types"
  20. "go.mau.fi/whatsmeow/types/events"
  21. )
  22. const (
  23. BusinessMessageLinkPrefix = "https://wa.me/message/"
  24. ContactQRLinkPrefix = "https://wa.me/qr/"
  25. BusinessMessageLinkDirectPrefix = "https://api.whatsapp.com/message/"
  26. ContactQRLinkDirectPrefix = "https://api.whatsapp.com/qr/"
  27. NewsletterLinkPrefix = "https://whatsapp.com/channel/"
  28. )
  29. // ResolveBusinessMessageLink resolves a business message short link and returns the target JID, business name and
  30. // text to prefill in the input field (if any).
  31. //
  32. // The links look like https://wa.me/message/<code> or https://api.whatsapp.com/message/<code>. You can either provide
  33. // the full link, or just the <code> part.
  34. func (cli *Client) ResolveBusinessMessageLink(ctx context.Context, code string) (*types.BusinessMessageLinkTarget, error) {
  35. code = strings.TrimPrefix(code, BusinessMessageLinkPrefix)
  36. code = strings.TrimPrefix(code, BusinessMessageLinkDirectPrefix)
  37. resp, err := cli.sendIQ(ctx, infoQuery{
  38. Namespace: "w:qr",
  39. Type: iqGet,
  40. // WhatsApp android doesn't seem to have a "to" field for this one at all, not sure why but it works
  41. Content: []waBinary.Node{{
  42. Tag: "qr",
  43. Attrs: waBinary.Attrs{
  44. "code": code,
  45. },
  46. }},
  47. })
  48. if errors.Is(err, ErrIQNotFound) {
  49. return nil, wrapIQError(ErrBusinessMessageLinkNotFound, err)
  50. } else if err != nil {
  51. return nil, err
  52. }
  53. qrChild, ok := resp.GetOptionalChildByTag("qr")
  54. if !ok {
  55. return nil, &ElementMissingError{Tag: "qr", In: "response to business message link query"}
  56. }
  57. var target types.BusinessMessageLinkTarget
  58. ag := qrChild.AttrGetter()
  59. target.JID = ag.JID("jid")
  60. target.PushName = ag.String("notify")
  61. messageChild, ok := qrChild.GetOptionalChildByTag("message")
  62. if ok {
  63. messageBytes, _ := messageChild.Content.([]byte)
  64. target.Message = string(messageBytes)
  65. }
  66. businessChild, ok := qrChild.GetOptionalChildByTag("business")
  67. if ok {
  68. bag := businessChild.AttrGetter()
  69. target.IsSigned = bag.OptionalBool("is_signed")
  70. target.VerifiedName = bag.OptionalString("verified_name")
  71. target.VerifiedLevel = bag.OptionalString("verified_level")
  72. }
  73. return &target, ag.Error()
  74. }
  75. // ResolveContactQRLink resolves a link from a contact share QR code and returns the target JID and push name.
  76. //
  77. // The links look like https://wa.me/qr/<code> or https://api.whatsapp.com/qr/<code>. You can either provide
  78. // the full link, or just the <code> part.
  79. func (cli *Client) ResolveContactQRLink(ctx context.Context, code string) (*types.ContactQRLinkTarget, error) {
  80. code = strings.TrimPrefix(code, ContactQRLinkPrefix)
  81. code = strings.TrimPrefix(code, ContactQRLinkDirectPrefix)
  82. resp, err := cli.sendIQ(ctx, infoQuery{
  83. Namespace: "w:qr",
  84. Type: iqGet,
  85. Content: []waBinary.Node{{
  86. Tag: "qr",
  87. Attrs: waBinary.Attrs{
  88. "code": code,
  89. },
  90. }},
  91. })
  92. if errors.Is(err, ErrIQNotFound) {
  93. return nil, wrapIQError(ErrContactQRLinkNotFound, err)
  94. } else if err != nil {
  95. return nil, err
  96. }
  97. qrChild, ok := resp.GetOptionalChildByTag("qr")
  98. if !ok {
  99. return nil, &ElementMissingError{Tag: "qr", In: "response to contact link query"}
  100. }
  101. var target types.ContactQRLinkTarget
  102. ag := qrChild.AttrGetter()
  103. target.JID = ag.JID("jid")
  104. target.PushName = ag.OptionalString("notify")
  105. target.Type = ag.String("type")
  106. return &target, ag.Error()
  107. }
  108. // GetContactQRLink gets your own contact share QR link that can be resolved using ResolveContactQRLink
  109. // (or scanned with the official apps when encoded as a QR code).
  110. //
  111. // If the revoke parameter is set to true, it will ask the server to revoke the previous link and generate a new one.
  112. func (cli *Client) GetContactQRLink(ctx context.Context, revoke bool) (string, error) {
  113. action := "get"
  114. if revoke {
  115. action = "revoke"
  116. }
  117. resp, err := cli.sendIQ(ctx, infoQuery{
  118. Namespace: "w:qr",
  119. Type: iqSet,
  120. Content: []waBinary.Node{{
  121. Tag: "qr",
  122. Attrs: waBinary.Attrs{
  123. "type": "contact",
  124. "action": action,
  125. },
  126. }},
  127. })
  128. if err != nil {
  129. return "", err
  130. }
  131. qrChild, ok := resp.GetOptionalChildByTag("qr")
  132. if !ok {
  133. return "", &ElementMissingError{Tag: "qr", In: "response to own contact link fetch"}
  134. }
  135. ag := qrChild.AttrGetter()
  136. return ag.String("code"), ag.Error()
  137. }
  138. // SetStatusMessage updates the current user's status text, which is shown in the "About" section in the user profile.
  139. //
  140. // This is different from the ephemeral status broadcast messages. Use SendMessage to types.StatusBroadcastJID to send
  141. // such messages.
  142. func (cli *Client) SetStatusMessage(ctx context.Context, msg string) error {
  143. _, err := cli.sendIQ(ctx, infoQuery{
  144. Namespace: "status",
  145. Type: iqSet,
  146. To: types.ServerJID,
  147. Content: []waBinary.Node{{
  148. Tag: "status",
  149. Content: msg,
  150. }},
  151. })
  152. return err
  153. }
  154. // IsOnWhatsApp checks if the given phone numbers are registered on WhatsApp.
  155. // The phone numbers should be in international format, including the `+` prefix.
  156. func (cli *Client) IsOnWhatsApp(ctx context.Context, phones []string) ([]types.IsOnWhatsAppResponse, error) {
  157. jids := make([]types.JID, len(phones))
  158. for i := range jids {
  159. jids[i] = types.NewJID(phones[i], types.LegacyUserServer)
  160. }
  161. list, err := cli.usync(ctx, jids, "query", "interactive", []waBinary.Node{
  162. {Tag: "business", Content: []waBinary.Node{{Tag: "verified_name"}}},
  163. {Tag: "contact"},
  164. })
  165. if err != nil {
  166. return nil, err
  167. }
  168. output := make([]types.IsOnWhatsAppResponse, 0, len(jids))
  169. querySuffix := "@" + types.LegacyUserServer
  170. for _, child := range list.GetChildren() {
  171. jid, jidOK := child.Attrs["jid"].(types.JID)
  172. if child.Tag != "user" || !jidOK {
  173. continue
  174. }
  175. var info types.IsOnWhatsAppResponse
  176. info.JID = jid
  177. info.VerifiedName, err = parseVerifiedName(child.GetChildByTag("business"))
  178. if err != nil {
  179. cli.Log.Warnf("Failed to parse %s's verified name details: %v", jid, err)
  180. }
  181. contactNode := child.GetChildByTag("contact")
  182. info.IsIn = contactNode.AttrGetter().String("type") == "in"
  183. contactQuery, _ := contactNode.Content.([]byte)
  184. info.Query = strings.TrimSuffix(string(contactQuery), querySuffix)
  185. output = append(output, info)
  186. }
  187. return output, nil
  188. }
  189. // GetUserInfo gets basic user info (avatar, status, verified business name, device list).
  190. func (cli *Client) GetUserInfo(ctx context.Context, jids []types.JID) (map[types.JID]types.UserInfo, error) {
  191. list, err := cli.usync(ctx, jids, "full", "background", []waBinary.Node{
  192. {Tag: "business", Content: []waBinary.Node{{Tag: "verified_name"}}},
  193. {Tag: "status"},
  194. {Tag: "picture"},
  195. {Tag: "devices", Attrs: waBinary.Attrs{"version": "2"}},
  196. {Tag: "lid"},
  197. })
  198. if err != nil {
  199. return nil, err
  200. }
  201. respData := make(map[types.JID]types.UserInfo, len(jids))
  202. mappings := make([]store.LIDMapping, 0, len(jids))
  203. for _, child := range list.GetChildren() {
  204. jid, jidOK := child.Attrs["jid"].(types.JID)
  205. if child.Tag != "user" || !jidOK {
  206. continue
  207. }
  208. var info types.UserInfo
  209. verifiedName, err := parseVerifiedName(child.GetChildByTag("business"))
  210. if err != nil {
  211. cli.Log.Warnf("Failed to parse %s's verified name details: %v", jid, err)
  212. }
  213. status, _ := child.GetChildByTag("status").Content.([]byte)
  214. info.Status = string(status)
  215. info.PictureID, _ = child.GetChildByTag("picture").Attrs["id"].(string)
  216. info.Devices = parseDeviceList(jid, child.GetChildByTag("devices"))
  217. lidTag := child.GetChildByTag("lid")
  218. info.LID = lidTag.AttrGetter().OptionalJIDOrEmpty("val")
  219. if !info.LID.IsEmpty() {
  220. mappings = append(mappings, store.LIDMapping{PN: jid, LID: info.LID})
  221. }
  222. if verifiedName != nil {
  223. cli.updateBusinessName(ctx, jid, info.LID, nil, verifiedName.Details.GetVerifiedName())
  224. }
  225. respData[jid] = info
  226. }
  227. err = cli.Store.LIDs.PutManyLIDMappings(ctx, mappings)
  228. if err != nil {
  229. // not worth returning on the error, instead just post a log
  230. cli.Log.Errorf("Failed to place LID mappings from USync call")
  231. }
  232. return respData, nil
  233. }
  234. func (cli *Client) GetBotListV2(ctx context.Context) ([]types.BotListInfo, error) {
  235. resp, err := cli.sendIQ(ctx, infoQuery{
  236. To: types.ServerJID,
  237. Namespace: "bot",
  238. Type: iqGet,
  239. Content: []waBinary.Node{
  240. {Tag: "bot", Attrs: waBinary.Attrs{"v": "2"}},
  241. },
  242. })
  243. if err != nil {
  244. return nil, err
  245. }
  246. botNode, ok := resp.GetOptionalChildByTag("bot")
  247. if !ok {
  248. return nil, &ElementMissingError{Tag: "bot", In: "response to bot list query"}
  249. }
  250. var list []types.BotListInfo
  251. for _, section := range botNode.GetChildrenByTag("section") {
  252. if section.AttrGetter().String("type") == "all" {
  253. for _, bot := range section.GetChildrenByTag("bot") {
  254. ag := bot.AttrGetter()
  255. list = append(list, types.BotListInfo{
  256. PersonaID: ag.String("persona_id"),
  257. BotJID: ag.JID("jid"),
  258. })
  259. }
  260. }
  261. }
  262. return list, nil
  263. }
  264. func (cli *Client) GetBotProfiles(ctx context.Context, botInfo []types.BotListInfo) ([]types.BotProfileInfo, error) {
  265. jids := make([]types.JID, len(botInfo))
  266. for i, bot := range botInfo {
  267. jids[i] = bot.BotJID
  268. }
  269. list, err := cli.usync(ctx, jids, "query", "interactive", []waBinary.Node{
  270. {Tag: "bot", Content: []waBinary.Node{{Tag: "profile", Attrs: waBinary.Attrs{"v": "1"}}}},
  271. }, UsyncQueryExtras{
  272. BotListInfo: botInfo,
  273. })
  274. if err != nil {
  275. return nil, err
  276. }
  277. var profiles []types.BotProfileInfo
  278. for _, user := range list.GetChildren() {
  279. jid := user.AttrGetter().JID("jid")
  280. bot := user.GetChildByTag("bot")
  281. profile := bot.GetChildByTag("profile")
  282. name := string(profile.GetChildByTag("name").Content.([]byte))
  283. attributes := string(profile.GetChildByTag("attributes").Content.([]byte))
  284. description := string(profile.GetChildByTag("description").Content.([]byte))
  285. category := string(profile.GetChildByTag("category").Content.([]byte))
  286. _, isDefault := profile.GetOptionalChildByTag("default")
  287. personaID := profile.AttrGetter().String("persona_id")
  288. commandsNode := profile.GetChildByTag("commands")
  289. commandDescription := string(commandsNode.GetChildByTag("description").Content.([]byte))
  290. var commands []types.BotProfileCommand
  291. for _, commandNode := range commandsNode.GetChildrenByTag("command") {
  292. commands = append(commands, types.BotProfileCommand{
  293. Name: string(commandNode.GetChildByTag("name").Content.([]byte)),
  294. Description: string(commandNode.GetChildByTag("description").Content.([]byte)),
  295. })
  296. }
  297. promptsNode := profile.GetChildByTag("prompts")
  298. var prompts []string
  299. for _, promptNode := range promptsNode.GetChildrenByTag("prompt") {
  300. prompts = append(
  301. prompts,
  302. fmt.Sprintf(
  303. "%s %s",
  304. string(promptNode.GetChildByTag("emoji").Content.([]byte)),
  305. string(promptNode.GetChildByTag("text").Content.([]byte)),
  306. ),
  307. )
  308. }
  309. profiles = append(profiles, types.BotProfileInfo{
  310. JID: jid,
  311. Name: name,
  312. Attributes: attributes,
  313. Description: description,
  314. Category: category,
  315. IsDefault: isDefault,
  316. Prompts: prompts,
  317. PersonaID: personaID,
  318. Commands: commands,
  319. CommandsDescription: commandDescription,
  320. })
  321. }
  322. return profiles, nil
  323. }
  324. func (cli *Client) parseBusinessProfile(node *waBinary.Node) (*types.BusinessProfile, error) {
  325. profileNode := node.GetChildByTag("profile")
  326. jid, ok := profileNode.AttrGetter().GetJID("jid", true)
  327. if !ok {
  328. return nil, errors.New("missing jid in business profile")
  329. }
  330. address, _ := profileNode.GetChildByTag("address").Content.([]byte)
  331. email, _ := profileNode.GetChildByTag("email").Content.([]byte)
  332. businessHour := profileNode.GetChildByTag("business_hours")
  333. businessHourTimezone := businessHour.AttrGetter().String("timezone")
  334. businessHoursConfigs := businessHour.GetChildren()
  335. businessHours := make([]types.BusinessHoursConfig, 0)
  336. for _, config := range businessHoursConfigs {
  337. if config.Tag != "business_hours_config" {
  338. continue
  339. }
  340. dow := config.AttrGetter().String("day_of_week")
  341. mode := config.AttrGetter().String("mode")
  342. openTime := config.AttrGetter().String("open_time")
  343. closeTime := config.AttrGetter().String("close_time")
  344. businessHours = append(businessHours, types.BusinessHoursConfig{
  345. DayOfWeek: dow,
  346. Mode: mode,
  347. OpenTime: openTime,
  348. CloseTime: closeTime,
  349. })
  350. }
  351. categoriesNode := profileNode.GetChildByTag("categories")
  352. categories := make([]types.Category, 0)
  353. for _, category := range categoriesNode.GetChildren() {
  354. if category.Tag != "category" {
  355. continue
  356. }
  357. id := category.AttrGetter().String("id")
  358. name, _ := category.Content.([]byte)
  359. categories = append(categories, types.Category{
  360. ID: id,
  361. Name: string(name),
  362. })
  363. }
  364. profileOptionsNode := profileNode.GetChildByTag("profile_options")
  365. profileOptions := make(map[string]string)
  366. for _, option := range profileOptionsNode.GetChildren() {
  367. optValueBytes, _ := option.Content.([]byte)
  368. profileOptions[option.Tag] = string(optValueBytes)
  369. // TODO parse bot_fields
  370. }
  371. return &types.BusinessProfile{
  372. JID: jid,
  373. Email: string(email),
  374. Address: string(address),
  375. Categories: categories,
  376. ProfileOptions: profileOptions,
  377. BusinessHoursTimeZone: businessHourTimezone,
  378. BusinessHours: businessHours,
  379. }, nil
  380. }
  381. // GetBusinessProfile gets the profile info of a WhatsApp business account
  382. func (cli *Client) GetBusinessProfile(ctx context.Context, jid types.JID) (*types.BusinessProfile, error) {
  383. resp, err := cli.sendIQ(ctx, infoQuery{
  384. Type: iqGet,
  385. To: types.ServerJID,
  386. Namespace: "w:biz",
  387. Content: []waBinary.Node{{
  388. Tag: "business_profile",
  389. Attrs: waBinary.Attrs{
  390. "v": "244",
  391. },
  392. Content: []waBinary.Node{{
  393. Tag: "profile",
  394. Attrs: waBinary.Attrs{
  395. "jid": jid,
  396. },
  397. }},
  398. }},
  399. })
  400. if err != nil {
  401. return nil, err
  402. }
  403. node, ok := resp.GetOptionalChildByTag("business_profile")
  404. if !ok {
  405. return nil, &ElementMissingError{Tag: "business_profile", In: "response to business profile query"}
  406. }
  407. return cli.parseBusinessProfile(&node)
  408. }
  409. func (cli *Client) GetUserDevicesContext(ctx context.Context, jids []types.JID) ([]types.JID, error) {
  410. return cli.GetUserDevices(ctx, jids)
  411. }
  412. // GetUserDevices gets the list of devices that the given user has. The input should be a list of
  413. // regular JIDs, and the output will be a list of AD JIDs. The local device will not be included in
  414. // the output even if the user's JID is included in the input. All other devices will be included.
  415. func (cli *Client) GetUserDevices(ctx context.Context, jids []types.JID) ([]types.JID, error) {
  416. if cli == nil {
  417. return nil, ErrClientIsNil
  418. }
  419. cli.userDevicesCacheLock.Lock()
  420. defer cli.userDevicesCacheLock.Unlock()
  421. var devices, jidsToSync, fbJIDsToSync []types.JID
  422. for _, jid := range jids {
  423. cached, ok := cli.userDevicesCache[jid]
  424. if ok && len(cached.devices) > 0 {
  425. devices = append(devices, cached.devices...)
  426. } else if jid.Server == types.MessengerServer {
  427. fbJIDsToSync = append(fbJIDsToSync, jid)
  428. } else if jid.IsBot() {
  429. // Bot JIDs do not have devices, the usync query is empty
  430. devices = append(devices, jid)
  431. } else {
  432. jidsToSync = append(jidsToSync, jid)
  433. }
  434. }
  435. if len(jidsToSync) > 0 {
  436. list, err := cli.usync(ctx, jidsToSync, "query", "message", []waBinary.Node{
  437. {Tag: "devices", Attrs: waBinary.Attrs{"version": "2"}},
  438. })
  439. if err != nil {
  440. return nil, err
  441. }
  442. for _, user := range list.GetChildren() {
  443. jid, jidOK := user.Attrs["jid"].(types.JID)
  444. if user.Tag != "user" || !jidOK {
  445. continue
  446. }
  447. userDevices := parseDeviceList(jid, user.GetChildByTag("devices"))
  448. cli.userDevicesCache[jid] = deviceCache{devices: userDevices, dhash: participantListHashV2(userDevices)}
  449. devices = append(devices, userDevices...)
  450. }
  451. }
  452. if len(fbJIDsToSync) > 0 {
  453. userDevices, err := cli.getFBIDDevices(ctx, fbJIDsToSync)
  454. if err != nil {
  455. return nil, err
  456. }
  457. devices = append(devices, userDevices...)
  458. }
  459. return devices, nil
  460. }
  461. type GetProfilePictureParams struct {
  462. Preview bool
  463. ExistingID string
  464. IsCommunity bool
  465. // This is a common group ID that you share with the target
  466. CommonGID types.JID
  467. // use this to query the profile photo of a group you don't have joined, but you have an invite code for
  468. InviteCode string
  469. // Persona ID when getting profile of Meta AI bots
  470. PersonaID string
  471. }
  472. // GetProfilePictureInfo gets the URL where you can download a WhatsApp user's profile picture or group's photo.
  473. //
  474. // Optionally, you can pass the last known profile picture ID.
  475. // If the profile picture hasn't changed, this will return nil with no error.
  476. //
  477. // To get a community photo, you should pass `IsCommunity: true`, as otherwise you may get a 401 error.
  478. func (cli *Client) GetProfilePictureInfo(ctx context.Context, jid types.JID, params *GetProfilePictureParams) (*types.ProfilePictureInfo, error) {
  479. if cli == nil {
  480. return nil, ErrClientIsNil
  481. }
  482. attrs := waBinary.Attrs{
  483. "query": "url",
  484. }
  485. var target, to types.JID
  486. if params == nil {
  487. params = &GetProfilePictureParams{}
  488. }
  489. if params.Preview {
  490. attrs["type"] = "preview"
  491. } else {
  492. attrs["type"] = "image"
  493. }
  494. if params.ExistingID != "" {
  495. attrs["id"] = params.ExistingID
  496. }
  497. if params.InviteCode != "" {
  498. attrs["invite"] = params.InviteCode
  499. }
  500. var expectWrapped bool
  501. var content []waBinary.Node
  502. namespace := "w:profile:picture"
  503. if params.IsCommunity {
  504. target = types.EmptyJID
  505. namespace = "w:g2"
  506. to = jid
  507. attrs["parent_group_jid"] = jid
  508. expectWrapped = true
  509. content = []waBinary.Node{{
  510. Tag: "pictures",
  511. Content: []waBinary.Node{{
  512. Tag: "picture",
  513. Attrs: attrs,
  514. }},
  515. }}
  516. } else {
  517. to = types.ServerJID
  518. target = jid
  519. if !params.CommonGID.IsEmpty() {
  520. attrs["common_gid"] = params.CommonGID
  521. }
  522. if params.PersonaID != "" {
  523. attrs["persona_id"] = params.PersonaID
  524. }
  525. var pictureContent []waBinary.Node
  526. if token, _ := cli.Store.PrivacyTokens.GetPrivacyToken(ctx, jid); token != nil {
  527. pictureContent = []waBinary.Node{{
  528. Tag: "tctoken",
  529. Content: token.Token,
  530. }}
  531. }
  532. content = []waBinary.Node{{
  533. Tag: "picture",
  534. Attrs: attrs,
  535. Content: pictureContent,
  536. }}
  537. }
  538. resp, err := cli.sendIQ(ctx, infoQuery{
  539. Namespace: namespace,
  540. Type: "get",
  541. To: to,
  542. Target: target,
  543. Content: content,
  544. })
  545. if errors.Is(err, ErrIQNotAuthorized) {
  546. return nil, wrapIQError(ErrProfilePictureUnauthorized, err)
  547. } else if errors.Is(err, ErrIQNotFound) {
  548. return nil, wrapIQError(ErrProfilePictureNotSet, err)
  549. } else if err != nil {
  550. return nil, err
  551. }
  552. if expectWrapped {
  553. pics, ok := resp.GetOptionalChildByTag("pictures")
  554. if !ok {
  555. return nil, &ElementMissingError{Tag: "pictures", In: "response to profile picture query"}
  556. }
  557. resp = &pics
  558. }
  559. picture, ok := resp.GetOptionalChildByTag("picture")
  560. if !ok {
  561. if params.ExistingID != "" {
  562. return nil, nil
  563. }
  564. return nil, &ElementMissingError{Tag: "picture", In: "response to profile picture query"}
  565. }
  566. var info types.ProfilePictureInfo
  567. ag := picture.AttrGetter()
  568. if ag.OptionalInt("status") == 304 {
  569. return nil, nil
  570. } else if ag.OptionalInt("status") == 204 {
  571. return nil, ErrProfilePictureNotSet
  572. }
  573. info.ID = ag.String("id")
  574. info.URL = ag.String("url")
  575. info.Type = ag.String("type")
  576. info.DirectPath = ag.String("direct_path")
  577. info.Hash, _ = base64.StdEncoding.DecodeString(ag.OptionalString("hash"))
  578. if !ag.OK() {
  579. return &info, ag.Error()
  580. }
  581. return &info, nil
  582. }
  583. func (cli *Client) handleHistoricalPushNames(ctx context.Context, names []*waHistorySync.Pushname) {
  584. if cli.Store.Contacts == nil {
  585. return
  586. }
  587. cli.Log.Infof("Updating contact store with %d push names from history sync", len(names))
  588. for _, user := range names {
  589. if user.GetPushname() == "-" {
  590. continue
  591. }
  592. var changed bool
  593. if jid, err := types.ParseJID(user.GetID()); err != nil {
  594. cli.Log.Warnf("Failed to parse user ID '%s' in push name history sync: %v", user.GetID(), err)
  595. } else if changed, _, err = cli.Store.Contacts.PutPushName(ctx, jid, user.GetPushname()); err != nil {
  596. cli.Log.Warnf("Failed to store push name of %s from history sync: %v", jid, err)
  597. } else if changed {
  598. cli.Log.Debugf("Got push name %s for %s in history sync", user.GetPushname(), jid)
  599. }
  600. }
  601. }
  602. func (cli *Client) updatePushName(ctx context.Context, user, userAlt types.JID, messageInfo *types.MessageInfo, name string) {
  603. if cli.Store.Contacts == nil {
  604. return
  605. }
  606. user = user.ToNonAD()
  607. changed, previousName, err := cli.Store.Contacts.PutPushName(ctx, user, name)
  608. if err != nil {
  609. cli.Log.Errorf("Failed to save push name of %s in device store: %v", user, err)
  610. } else if changed {
  611. userAlt = userAlt.ToNonAD()
  612. if userAlt.IsEmpty() {
  613. userAlt, _ = cli.Store.GetAltJID(ctx, user)
  614. }
  615. if !userAlt.IsEmpty() {
  616. _, _, err = cli.Store.Contacts.PutPushName(ctx, userAlt, name)
  617. if err != nil {
  618. cli.Log.Errorf("Failed to save push name of %s in device store: %v", userAlt, err)
  619. }
  620. }
  621. cli.Log.Debugf("Push name of %s changed from %s to %s, dispatching event", user, previousName, name)
  622. cli.dispatchEvent(&events.PushName{
  623. JID: user,
  624. JIDAlt: userAlt,
  625. Message: messageInfo,
  626. OldPushName: previousName,
  627. NewPushName: name,
  628. })
  629. }
  630. }
  631. func (cli *Client) updateBusinessName(ctx context.Context, user, userAlt types.JID, messageInfo *types.MessageInfo, name string) {
  632. if cli.Store.Contacts == nil {
  633. return
  634. }
  635. changed, previousName, err := cli.Store.Contacts.PutBusinessName(ctx, user, name)
  636. if err != nil {
  637. cli.Log.Errorf("Failed to save business name of %s in device store: %v", user, err)
  638. } else if changed {
  639. userAlt = userAlt.ToNonAD()
  640. if userAlt.IsEmpty() {
  641. userAlt, _ = cli.Store.GetAltJID(ctx, user)
  642. }
  643. if !userAlt.IsEmpty() {
  644. _, _, err = cli.Store.Contacts.PutBusinessName(ctx, userAlt, name)
  645. if err != nil {
  646. cli.Log.Errorf("Failed to save push name of %s in device store: %v", userAlt, err)
  647. }
  648. }
  649. cli.Log.Debugf("Business name of %s changed from %s to %s, dispatching event", user, previousName, name)
  650. cli.dispatchEvent(&events.BusinessName{
  651. JID: user,
  652. Message: messageInfo,
  653. OldBusinessName: previousName,
  654. NewBusinessName: name,
  655. })
  656. }
  657. }
  658. func parseVerifiedName(businessNode waBinary.Node) (*types.VerifiedName, error) {
  659. if businessNode.Tag != "business" {
  660. return nil, nil
  661. }
  662. verifiedNameNode, ok := businessNode.GetOptionalChildByTag("verified_name")
  663. if !ok {
  664. return nil, nil
  665. }
  666. return parseVerifiedNameContent(verifiedNameNode)
  667. }
  668. func parseVerifiedNameContent(verifiedNameNode waBinary.Node) (*types.VerifiedName, error) {
  669. rawCert, ok := verifiedNameNode.Content.([]byte)
  670. if !ok {
  671. return nil, nil
  672. }
  673. var cert waVnameCert.VerifiedNameCertificate
  674. err := proto.Unmarshal(rawCert, &cert)
  675. if err != nil {
  676. return nil, err
  677. }
  678. var certDetails waVnameCert.VerifiedNameCertificate_Details
  679. err = proto.Unmarshal(cert.GetDetails(), &certDetails)
  680. if err != nil {
  681. return nil, err
  682. }
  683. return &types.VerifiedName{
  684. Certificate: &cert,
  685. Details: &certDetails,
  686. }, nil
  687. }
  688. func parseDeviceList(user types.JID, deviceNode waBinary.Node) []types.JID {
  689. deviceList := deviceNode.GetChildByTag("device-list")
  690. if deviceNode.Tag != "devices" || deviceList.Tag != "device-list" {
  691. return nil
  692. }
  693. children := deviceList.GetChildren()
  694. devices := make([]types.JID, 0, len(children))
  695. for _, device := range children {
  696. deviceID, ok := device.AttrGetter().GetInt64("id", true)
  697. isHosted := device.AttrGetter().Bool("is_hosted")
  698. if device.Tag != "device" || !ok {
  699. continue
  700. }
  701. user.Device = uint16(deviceID)
  702. if isHosted {
  703. hostedUser := user
  704. if user.Server == types.HiddenUserServer {
  705. hostedUser.Server = types.HostedLIDServer
  706. } else {
  707. hostedUser.Server = types.HostedServer
  708. }
  709. devices = append(devices, hostedUser)
  710. } else {
  711. devices = append(devices, user)
  712. }
  713. }
  714. return devices
  715. }
  716. func parseFBDeviceList(user types.JID, deviceList waBinary.Node) deviceCache {
  717. children := deviceList.GetChildren()
  718. devices := make([]types.JID, 0, len(children))
  719. for _, device := range children {
  720. deviceID, ok := device.AttrGetter().GetInt64("id", true)
  721. if device.Tag != "device" || !ok {
  722. continue
  723. }
  724. user.Device = uint16(deviceID)
  725. devices = append(devices, user)
  726. // TODO take identities here too?
  727. }
  728. // TODO do something with the icdc blob?
  729. return deviceCache{
  730. devices: devices,
  731. dhash: deviceList.AttrGetter().String("dhash"),
  732. }
  733. }
  734. func (cli *Client) getFBIDDevicesInternal(ctx context.Context, jids []types.JID) (*waBinary.Node, error) {
  735. users := make([]waBinary.Node, len(jids))
  736. for i, jid := range jids {
  737. users[i].Tag = "user"
  738. users[i].Attrs = waBinary.Attrs{"jid": jid}
  739. // TODO include dhash for users
  740. }
  741. resp, err := cli.sendIQ(ctx, infoQuery{
  742. Namespace: "fbid:devices",
  743. Type: iqGet,
  744. To: types.ServerJID,
  745. Content: []waBinary.Node{{
  746. Tag: "users",
  747. Content: users,
  748. }},
  749. })
  750. if err != nil {
  751. return nil, fmt.Errorf("failed to send usync query: %w", err)
  752. } else if list, ok := resp.GetOptionalChildByTag("users"); !ok {
  753. return nil, &ElementMissingError{Tag: "users", In: "response to fbid devices query"}
  754. } else {
  755. return &list, err
  756. }
  757. }
  758. func (cli *Client) getFBIDDevices(ctx context.Context, jids []types.JID) ([]types.JID, error) {
  759. var devices []types.JID
  760. for chunk := range slices.Chunk(jids, 15) {
  761. list, err := cli.getFBIDDevicesInternal(ctx, chunk)
  762. if err != nil {
  763. return nil, err
  764. }
  765. for _, user := range list.GetChildren() {
  766. jid, jidOK := user.Attrs["jid"].(types.JID)
  767. if user.Tag != "user" || !jidOK {
  768. continue
  769. }
  770. userDevices := parseFBDeviceList(jid, user.GetChildByTag("devices"))
  771. cli.userDevicesCache[jid] = userDevices
  772. devices = append(devices, userDevices.devices...)
  773. }
  774. }
  775. return devices, nil
  776. }
  777. type UsyncQueryExtras struct {
  778. BotListInfo []types.BotListInfo
  779. }
  780. func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context string, query []waBinary.Node, extra ...UsyncQueryExtras) (*waBinary.Node, error) {
  781. if cli == nil {
  782. return nil, ErrClientIsNil
  783. }
  784. var extras UsyncQueryExtras
  785. if len(extra) > 1 {
  786. return nil, errors.New("only one extra parameter may be provided to usync()")
  787. } else if len(extra) == 1 {
  788. extras = extra[0]
  789. }
  790. userList := make([]waBinary.Node, len(jids))
  791. for i, jid := range jids {
  792. userList[i].Tag = "user"
  793. jid = jid.ToNonAD()
  794. switch jid.Server {
  795. case types.LegacyUserServer:
  796. userList[i].Content = []waBinary.Node{{
  797. Tag: "contact",
  798. Content: jid.String(),
  799. }}
  800. case types.DefaultUserServer, types.HiddenUserServer:
  801. // NOTE: You can pass in an LID with a JID (<lid jid=...> user node)
  802. // Not sure if you can just put the LID in the jid tag here (works for <devices> queries mainly)
  803. userList[i].Attrs = waBinary.Attrs{"jid": jid}
  804. if jid.IsBot() {
  805. var personaID string
  806. for _, bot := range extras.BotListInfo {
  807. if bot.BotJID.User == jid.User {
  808. personaID = bot.PersonaID
  809. }
  810. }
  811. userList[i].Content = []waBinary.Node{{
  812. Tag: "bot",
  813. Content: []waBinary.Node{{
  814. Tag: "profile",
  815. Attrs: waBinary.Attrs{"persona_id": personaID},
  816. }},
  817. }}
  818. }
  819. default:
  820. return nil, fmt.Errorf("unknown user server '%s'", jid.Server)
  821. }
  822. }
  823. resp, err := cli.sendIQ(ctx, infoQuery{
  824. Namespace: "usync",
  825. Type: "get",
  826. To: types.ServerJID,
  827. Content: []waBinary.Node{{
  828. Tag: "usync",
  829. Attrs: waBinary.Attrs{
  830. "sid": cli.generateRequestID(),
  831. "mode": mode,
  832. "last": "true",
  833. "index": "0",
  834. "context": context,
  835. },
  836. Content: []waBinary.Node{
  837. {Tag: "query", Content: query},
  838. {Tag: "list", Content: userList},
  839. },
  840. }},
  841. })
  842. if err != nil {
  843. return nil, fmt.Errorf("failed to send usync query: %w", err)
  844. } else if list, ok := resp.GetOptionalChildByTag("usync", "list"); !ok {
  845. return nil, &ElementMissingError{Tag: "list", In: "response to usync query"}
  846. } else {
  847. return &list, err
  848. }
  849. }
  850. func (cli *Client) parseBlocklist(node *waBinary.Node) *types.Blocklist {
  851. output := &types.Blocklist{
  852. DHash: node.AttrGetter().String("dhash"),
  853. }
  854. for _, child := range node.GetChildren() {
  855. ag := child.AttrGetter()
  856. blockedJID := ag.JID("jid")
  857. if !ag.OK() {
  858. cli.Log.Debugf("Ignoring contact blocked data with unexpected attributes: %v", ag.Error())
  859. continue
  860. }
  861. output.JIDs = append(output.JIDs, blockedJID)
  862. }
  863. return output
  864. }
  865. // GetBlocklist gets the list of users that this user has blocked.
  866. func (cli *Client) GetBlocklist(ctx context.Context) (*types.Blocklist, error) {
  867. resp, err := cli.sendIQ(ctx, infoQuery{
  868. Namespace: "blocklist",
  869. Type: iqGet,
  870. To: types.ServerJID,
  871. })
  872. if err != nil {
  873. return nil, err
  874. }
  875. list, ok := resp.GetOptionalChildByTag("list")
  876. if !ok {
  877. return nil, &ElementMissingError{Tag: "list", In: "response to blocklist query"}
  878. }
  879. return cli.parseBlocklist(&list), nil
  880. }
  881. // UpdateBlocklist updates the user's block list and returns the updated list.
  882. func (cli *Client) UpdateBlocklist(ctx context.Context, jid types.JID, action events.BlocklistChangeAction) (*types.Blocklist, error) {
  883. resp, err := cli.sendIQ(ctx, infoQuery{
  884. Namespace: "blocklist",
  885. Type: iqSet,
  886. To: types.ServerJID,
  887. Content: []waBinary.Node{{
  888. Tag: "item",
  889. Attrs: waBinary.Attrs{
  890. "jid": jid,
  891. "action": string(action),
  892. },
  893. }},
  894. })
  895. if err != nil {
  896. return nil, err
  897. }
  898. list, ok := resp.GetOptionalChildByTag("list")
  899. if !ok {
  900. return nil, &ElementMissingError{Tag: "list", In: "response to blocklist update"}
  901. }
  902. return cli.parseBlocklist(&list), err
  903. }