clientpayload.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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 store
  7. import (
  8. "crypto/md5"
  9. "encoding/binary"
  10. "fmt"
  11. "strconv"
  12. "strings"
  13. "google.golang.org/protobuf/proto"
  14. "go.mau.fi/libsignal/ecc"
  15. "go.mau.fi/whatsmeow/proto/waCompanionReg"
  16. "go.mau.fi/whatsmeow/proto/waWa6"
  17. "go.mau.fi/whatsmeow/types"
  18. )
  19. // WAVersionContainer is a container for a WhatsApp web version number.
  20. type WAVersionContainer [3]uint32
  21. // ParseVersion parses a version string (three dot-separated numbers) into a WAVersionContainer.
  22. func ParseVersion(version string) (parsed WAVersionContainer, err error) {
  23. var part1, part2, part3 int
  24. if parts := strings.Split(version, "."); len(parts) != 3 {
  25. err = fmt.Errorf("'%s' doesn't contain three dot-separated parts", version)
  26. } else if part1, err = strconv.Atoi(parts[0]); err != nil {
  27. err = fmt.Errorf("first part of '%s' is not a number: %w", version, err)
  28. } else if part2, err = strconv.Atoi(parts[1]); err != nil {
  29. err = fmt.Errorf("second part of '%s' is not a number: %w", version, err)
  30. } else if part3, err = strconv.Atoi(parts[2]); err != nil {
  31. err = fmt.Errorf("third part of '%s' is not a number: %w", version, err)
  32. } else {
  33. parsed = WAVersionContainer{uint32(part1), uint32(part2), uint32(part3)}
  34. }
  35. return
  36. }
  37. func (vc WAVersionContainer) LessThan(other WAVersionContainer) bool {
  38. return vc[0] < other[0] ||
  39. (vc[0] == other[0] && vc[1] < other[1]) ||
  40. (vc[0] == other[0] && vc[1] == other[1] && vc[2] < other[2])
  41. }
  42. // IsZero returns true if the version is zero.
  43. func (vc WAVersionContainer) IsZero() bool {
  44. return vc == [3]uint32{0, 0, 0}
  45. }
  46. // String returns the version number as a dot-separated string.
  47. func (vc WAVersionContainer) String() string {
  48. parts := make([]string, len(vc))
  49. for i, part := range vc {
  50. parts[i] = strconv.Itoa(int(part))
  51. }
  52. return strings.Join(parts, ".")
  53. }
  54. // Hash returns the md5 hash of the String representation of this version.
  55. func (vc WAVersionContainer) Hash() [16]byte {
  56. return md5.Sum([]byte(vc.String()))
  57. }
  58. func (vc WAVersionContainer) ProtoAppVersion() *waWa6.ClientPayload_UserAgent_AppVersion {
  59. return &waWa6.ClientPayload_UserAgent_AppVersion{
  60. Primary: &vc[0],
  61. Secondary: &vc[1],
  62. Tertiary: &vc[2],
  63. }
  64. }
  65. // waVersion is the WhatsApp web client version
  66. var waVersion = WAVersionContainer{2, 3000, 1030403648}
  67. // waVersionHash is the md5 hash of a dot-separated waVersion
  68. var waVersionHash [16]byte
  69. func init() {
  70. waVersionHash = waVersion.Hash()
  71. }
  72. // GetWAVersion gets the current WhatsApp web client version.
  73. func GetWAVersion() WAVersionContainer {
  74. return waVersion
  75. }
  76. // SetWAVersion sets the current WhatsApp web client version.
  77. //
  78. // In general, you should keep the library up-to-date instead of using this,
  79. // as there may be code changes that are necessary too (like protobuf schema changes).
  80. func SetWAVersion(version WAVersionContainer) {
  81. if version.IsZero() {
  82. return
  83. }
  84. waVersion = version
  85. waVersionHash = version.Hash()
  86. }
  87. var BaseClientPayload = &waWa6.ClientPayload{
  88. UserAgent: &waWa6.ClientPayload_UserAgent{
  89. Platform: waWa6.ClientPayload_UserAgent_WEB.Enum(),
  90. ReleaseChannel: waWa6.ClientPayload_UserAgent_RELEASE.Enum(),
  91. AppVersion: waVersion.ProtoAppVersion(),
  92. Mcc: proto.String("000"),
  93. Mnc: proto.String("000"),
  94. OsVersion: proto.String("0.1"),
  95. Manufacturer: proto.String(""),
  96. Device: proto.String("Desktop"),
  97. OsBuildNumber: proto.String("0.1"),
  98. LocaleLanguageIso6391: proto.String("en"),
  99. LocaleCountryIso31661Alpha2: proto.String("US"),
  100. },
  101. WebInfo: &waWa6.ClientPayload_WebInfo{
  102. WebSubPlatform: waWa6.ClientPayload_WebInfo_WEB_BROWSER.Enum(),
  103. },
  104. ConnectType: waWa6.ClientPayload_WIFI_UNKNOWN.Enum(),
  105. ConnectReason: waWa6.ClientPayload_USER_ACTIVATED.Enum(),
  106. }
  107. var DeviceProps = &waCompanionReg.DeviceProps{
  108. Os: proto.String("whatsmeow"),
  109. Version: &waCompanionReg.DeviceProps_AppVersion{
  110. Primary: proto.Uint32(0),
  111. Secondary: proto.Uint32(1),
  112. Tertiary: proto.Uint32(0),
  113. },
  114. HistorySyncConfig: &waCompanionReg.DeviceProps_HistorySyncConfig{
  115. StorageQuotaMb: proto.Uint32(10240),
  116. InlineInitialPayloadInE2EeMsg: proto.Bool(true),
  117. RecentSyncDaysLimit: nil,
  118. SupportCallLogHistory: proto.Bool(false),
  119. SupportBotUserAgentChatHistory: proto.Bool(true),
  120. SupportCagReactionsAndPolls: proto.Bool(true),
  121. SupportBizHostedMsg: proto.Bool(true),
  122. SupportRecentSyncChunkMessageCountTuning: proto.Bool(true),
  123. SupportHostedGroupMsg: proto.Bool(true),
  124. SupportFbidBotChatHistory: proto.Bool(true),
  125. SupportAddOnHistorySyncMigration: nil,
  126. SupportMessageAssociation: proto.Bool(true),
  127. SupportGroupHistory: proto.Bool(false),
  128. OnDemandReady: nil,
  129. SupportGuestChat: nil,
  130. CompleteOnDemandReady: nil,
  131. ThumbnailSyncDaysLimit: nil,
  132. },
  133. PlatformType: waCompanionReg.DeviceProps_UNKNOWN.Enum(),
  134. RequireFullSync: proto.Bool(false),
  135. }
  136. func SetOSInfo(name string, version [3]uint32) {
  137. DeviceProps.Os = &name
  138. DeviceProps.Version.Primary = &version[0]
  139. DeviceProps.Version.Secondary = &version[1]
  140. DeviceProps.Version.Tertiary = &version[2]
  141. BaseClientPayload.UserAgent.OsVersion = proto.String(fmt.Sprintf("%d.%d.%d", version[0], version[1], version[2]))
  142. BaseClientPayload.UserAgent.OsBuildNumber = BaseClientPayload.UserAgent.OsVersion
  143. }
  144. func (device *Device) getRegistrationPayload() *waWa6.ClientPayload {
  145. payload := proto.Clone(BaseClientPayload).(*waWa6.ClientPayload)
  146. regID := make([]byte, 4)
  147. binary.BigEndian.PutUint32(regID, device.RegistrationID)
  148. preKeyID := make([]byte, 4)
  149. binary.BigEndian.PutUint32(preKeyID, device.SignedPreKey.KeyID)
  150. deviceProps, _ := proto.Marshal(DeviceProps)
  151. payload.DevicePairingData = &waWa6.ClientPayload_DevicePairingRegistrationData{
  152. ERegid: regID,
  153. EKeytype: []byte{ecc.DjbType},
  154. EIdent: device.IdentityKey.Pub[:],
  155. ESkeyID: preKeyID[1:],
  156. ESkeyVal: device.SignedPreKey.Pub[:],
  157. ESkeySig: device.SignedPreKey.Signature[:],
  158. BuildHash: waVersionHash[:],
  159. DeviceProps: deviceProps,
  160. }
  161. payload.Passive = proto.Bool(false)
  162. payload.Pull = proto.Bool(false)
  163. return payload
  164. }
  165. func (device *Device) getLoginPayload() *waWa6.ClientPayload {
  166. payload := proto.Clone(BaseClientPayload).(*waWa6.ClientPayload)
  167. payload.Username = proto.Uint64(device.ID.UserInt())
  168. payload.Device = proto.Uint32(uint32(device.ID.Device))
  169. payload.Passive = proto.Bool(true)
  170. payload.Pull = proto.Bool(true)
  171. payload.LidDbMigrated = proto.Bool(true)
  172. return payload
  173. }
  174. func (device *Device) GetClientPayload() *waWa6.ClientPayload {
  175. if device.ID != nil {
  176. if device.Mobile {
  177. return device.ClientPayload
  178. }
  179. if *device.ID == types.EmptyJID {
  180. panic(fmt.Errorf("GetClientPayload called with empty JID"))
  181. }
  182. return device.getLoginPayload()
  183. } else {
  184. return device.getRegistrationPayload()
  185. }
  186. }