client.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  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 implements a client for interacting with the WhatsApp web multidevice API.
  7. package whatsmeow
  8. import (
  9. "context"
  10. "encoding/base64"
  11. "encoding/hex"
  12. "encoding/json"
  13. "errors"
  14. "fmt"
  15. "git.bobomao.top/joey/testwh/app"
  16. "net"
  17. "net/http"
  18. "net/url"
  19. "runtime/debug"
  20. "sync"
  21. "sync/atomic"
  22. "time"
  23. "go.mau.fi/util/exhttp"
  24. "go.mau.fi/util/exsync"
  25. "go.mau.fi/util/ptr"
  26. "go.mau.fi/util/random"
  27. "golang.org/x/net/proxy"
  28. "git.bobomao.top/joey/testwh/appstate"
  29. waBinary "git.bobomao.top/joey/testwh/binary"
  30. "git.bobomao.top/joey/testwh/proto/waE2E"
  31. "git.bobomao.top/joey/testwh/proto/waWa6"
  32. "git.bobomao.top/joey/testwh/proto/waWeb"
  33. "git.bobomao.top/joey/testwh/socket"
  34. "git.bobomao.top/joey/testwh/store"
  35. "git.bobomao.top/joey/testwh/types"
  36. "git.bobomao.top/joey/testwh/types/events"
  37. "git.bobomao.top/joey/testwh/util/keys"
  38. waLog "git.bobomao.top/joey/testwh/util/log"
  39. )
  40. // EventHandler is a function that can handle events from WhatsApp.
  41. type EventHandler func(evt any)
  42. type EventHandlerWithSuccessStatus func(evt any) bool
  43. type nodeHandler func(ctx context.Context, node *waBinary.Node)
  44. var nextHandlerID uint32
  45. type wrappedEventHandler struct {
  46. fn EventHandlerWithSuccessStatus
  47. id uint32
  48. }
  49. type deviceCache struct {
  50. devices []types.JID
  51. dhash string
  52. }
  53. // Client contains everything necessary to connect to and interact with the WhatsApp web API.
  54. type Client struct {
  55. Store *store.Device
  56. Log waLog.Logger
  57. recvLog waLog.Logger
  58. sendLog waLog.Logger
  59. socket *socket.NoiseSocket
  60. socketLock sync.RWMutex
  61. socketWait chan struct{}
  62. isLoggedIn atomic.Bool
  63. expectedDisconnect *exsync.Event
  64. EnableAutoReconnect bool
  65. InitialAutoReconnect bool
  66. LastSuccessfulConnect time.Time
  67. AutoReconnectErrors int
  68. // AutoReconnectHook is called when auto-reconnection fails. If the function returns false,
  69. // the client will not attempt to reconnect. The number of retries can be read from AutoReconnectErrors.
  70. AutoReconnectHook func(error) bool
  71. // If SynchronousAck is set, acks for messages will only be sent after all event handlers return.
  72. SynchronousAck bool
  73. EnableDecryptedEventBuffer bool
  74. lastDecryptedBufferClear time.Time
  75. DisableLoginAutoReconnect bool
  76. sendActiveReceipts atomic.Uint32
  77. // EmitAppStateEventsOnFullSync can be set to true if you want to get app state events emitted
  78. // even when re-syncing the whole state.
  79. EmitAppStateEventsOnFullSync bool
  80. AutomaticMessageRerequestFromPhone bool
  81. pendingPhoneRerequests map[types.MessageID]context.CancelFunc
  82. pendingPhoneRerequestsLock sync.RWMutex
  83. appStateProc *appstate.Processor
  84. appStateSyncLock sync.Mutex
  85. historySyncNotifications chan *waE2E.HistorySyncNotification
  86. historySyncHandlerStarted atomic.Bool
  87. ManualHistorySyncDownload bool
  88. uploadPreKeysLock sync.Mutex
  89. lastPreKeyUpload time.Time
  90. mediaConnCache *MediaConn
  91. mediaConnLock sync.Mutex
  92. responseWaiters map[string]chan<- *waBinary.Node
  93. responseWaitersLock sync.Mutex
  94. nodeHandlers map[string]nodeHandler
  95. handlerQueue chan *waBinary.Node
  96. eventHandlers []wrappedEventHandler
  97. eventHandlersLock sync.RWMutex
  98. messageRetries map[string]int
  99. messageRetriesLock sync.Mutex
  100. incomingRetryRequestCounter map[incomingRetryKey]int
  101. incomingRetryRequestCounterLock sync.Mutex
  102. appStateKeyRequests map[string]time.Time
  103. appStateKeyRequestsLock sync.RWMutex
  104. messageSendLock sync.Mutex
  105. privacySettingsCache atomic.Value
  106. groupCache map[types.JID]*groupMetaCache
  107. groupCacheLock sync.Mutex
  108. userDevicesCache map[types.JID]deviceCache
  109. userDevicesCacheLock sync.Mutex
  110. recentMessagesMap map[recentMessageKey]RecentMessage
  111. recentMessagesList [recentMessagesSize]recentMessageKey
  112. recentMessagesPtr int
  113. recentMessagesLock sync.RWMutex
  114. sessionRecreateHistory map[types.JID]time.Time
  115. sessionRecreateHistoryLock sync.Mutex
  116. // GetMessageForRetry is used to find the source message for handling retry receipts
  117. // when the message is not found in the recently sent message cache.
  118. GetMessageForRetry func(requester, to types.JID, id types.MessageID) *waE2E.Message
  119. // PreRetryCallback is called before a retry receipt is accepted.
  120. // If it returns false, the accepting will be cancelled and the retry receipt will be ignored.
  121. PreRetryCallback func(receipt *events.Receipt, id types.MessageID, retryCount int, msg *waE2E.Message) bool
  122. // PrePairCallback is called before pairing is completed. If it returns false, the pairing will be cancelled and
  123. // the client will disconnect.
  124. PrePairCallback func(jid types.JID, platform, businessName string) bool
  125. // GetClientPayload is called to get the client payload for connecting to the server.
  126. // This should NOT be used for WhatsApp (to change the OS name, update fields in store.BaseClientPayload directly).
  127. GetClientPayload func() *waWa6.ClientPayload
  128. // Should untrusted identity errors be handled automatically? If true, the stored identity and existing signal
  129. // sessions will be removed on untrusted identity errors, and an events.IdentityChange will be dispatched.
  130. // If false, decrypting a message from untrusted devices will fail.
  131. AutoTrustIdentity bool
  132. // Should SubscribePresence return an error if no privacy token is stored for the user?
  133. ErrorOnSubscribePresenceWithoutToken bool
  134. SendReportingTokens bool
  135. BackgroundEventCtx context.Context
  136. phoneLinkingCache *phoneLinkingCache
  137. uniqueID string
  138. idCounter atomic.Uint64
  139. mediaHTTP *http.Client
  140. websocketHTTP *http.Client
  141. preLoginHTTP *http.Client
  142. // This field changes the client to act like a Messenger client instead of a WhatsApp one.
  143. //
  144. // Note that you cannot use a Messenger account just by setting this field, you must use a
  145. // separate library for all the non-e2ee-related stuff like logging in.
  146. // The library is currently embedded in mautrix-meta (https://github.com/mautrix/meta), but may be separated later.
  147. MessengerConfig *MessengerConfig
  148. RefreshCAT func(context.Context) error
  149. }
  150. type groupMetaCache struct {
  151. AddressingMode types.AddressingMode
  152. CommunityAnnouncementGroup bool
  153. Members []types.JID
  154. }
  155. type MessengerConfig struct {
  156. UserAgent string
  157. BaseURL string
  158. WebsocketURL string
  159. }
  160. // Size of buffer for the channel that all incoming XML nodes go through.
  161. // In general it shouldn't go past a few buffered messages, but the channel is big to be safe.
  162. const handlerQueueSize = 2048
  163. // NewClient initializes a new WhatsApp web client.
  164. //
  165. // The logger can be nil, it will default to a no-op logger.
  166. //
  167. // The device store must be set. A default SQL-backed implementation is available in the store/sqlstore package.
  168. //
  169. // container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
  170. // if err != nil {
  171. // panic(err)
  172. // }
  173. // // If you want multiple sessions, remember their JIDs and use .GetDevice(jid) or .GetAllDevices() instead.
  174. // deviceStore, err := container.GetFirstDevice()
  175. // if err != nil {
  176. // panic(err)
  177. // }
  178. // client := whatsmeow.NewClient(deviceStore, nil)
  179. func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
  180. if log == nil {
  181. log = waLog.Noop
  182. }
  183. uniqueIDPrefix := random.Bytes(2)
  184. baseHTTPClient := &http.Client{
  185. Transport: (http.DefaultTransport.(*http.Transport)).Clone(),
  186. }
  187. cli := &Client{
  188. mediaHTTP: ptr.Clone(baseHTTPClient),
  189. websocketHTTP: ptr.Clone(baseHTTPClient),
  190. preLoginHTTP: ptr.Clone(baseHTTPClient),
  191. Store: deviceStore,
  192. Log: log,
  193. recvLog: log.Sub("Recv"),
  194. sendLog: log.Sub("Send"),
  195. uniqueID: fmt.Sprintf("%d.%d-", uniqueIDPrefix[0], uniqueIDPrefix[1]),
  196. responseWaiters: make(map[string]chan<- *waBinary.Node),
  197. eventHandlers: make([]wrappedEventHandler, 0, 1),
  198. messageRetries: make(map[string]int),
  199. handlerQueue: make(chan *waBinary.Node, handlerQueueSize),
  200. appStateProc: appstate.NewProcessor(deviceStore, log.Sub("AppState")),
  201. socketWait: make(chan struct{}),
  202. expectedDisconnect: exsync.NewEvent(),
  203. incomingRetryRequestCounter: make(map[incomingRetryKey]int),
  204. historySyncNotifications: make(chan *waE2E.HistorySyncNotification, 32),
  205. groupCache: make(map[types.JID]*groupMetaCache),
  206. userDevicesCache: make(map[types.JID]deviceCache),
  207. recentMessagesMap: make(map[recentMessageKey]RecentMessage, recentMessagesSize),
  208. sessionRecreateHistory: make(map[types.JID]time.Time),
  209. GetMessageForRetry: func(requester, to types.JID, id types.MessageID) *waE2E.Message { return nil },
  210. appStateKeyRequests: make(map[string]time.Time),
  211. pendingPhoneRerequests: make(map[types.MessageID]context.CancelFunc),
  212. EnableAutoReconnect: true,
  213. AutoTrustIdentity: true,
  214. BackgroundEventCtx: context.Background(),
  215. }
  216. cli.nodeHandlers = map[string]nodeHandler{
  217. "message": cli.handleEncryptedMessage,
  218. "appdata": cli.handleEncryptedMessage,
  219. "receipt": cli.handleReceipt,
  220. "call": cli.handleCallEvent,
  221. "chatstate": cli.handleChatState,
  222. "presence": cli.handlePresence,
  223. "notification": cli.handleNotification,
  224. "success": cli.handleConnectSuccess,
  225. "failure": cli.handleConnectFailure,
  226. "stream:error": cli.handleStreamError,
  227. "iq": cli.handleIQ,
  228. "ib": cli.handleIB,
  229. // Apparently there's also an <error> node which can have a code=479 and means "Invalid stanza sent (smax-invalid)"
  230. }
  231. return cli
  232. }
  233. // SetProxyAddress is a helper method that parses a URL string and calls SetProxy or SetSOCKSProxy based on the URL scheme.
  234. //
  235. // Returns an error if url.Parse fails to parse the given address.
  236. func (cli *Client) SetProxyAddress(addr string, opts ...SetProxyOptions) error {
  237. if addr == "" {
  238. cli.SetProxy(nil, opts...)
  239. return nil
  240. }
  241. parsed, err := url.Parse(addr)
  242. if err != nil {
  243. return err
  244. }
  245. if parsed.Scheme == "http" || parsed.Scheme == "https" {
  246. cli.SetProxy(http.ProxyURL(parsed), opts...)
  247. } else if parsed.Scheme == "socks5" {
  248. px, err := proxy.FromURL(parsed, &net.Dialer{
  249. Timeout: 30 * time.Second,
  250. KeepAlive: 30 * time.Second,
  251. })
  252. if err != nil {
  253. return err
  254. }
  255. cli.SetSOCKSProxy(px, opts...)
  256. } else {
  257. return fmt.Errorf("unsupported proxy scheme %q", parsed.Scheme)
  258. }
  259. return nil
  260. }
  261. type Proxy = func(*http.Request) (*url.URL, error)
  262. // SetProxy sets a HTTP proxy to use for WhatsApp web websocket connections and media uploads/downloads.
  263. //
  264. // Must be called before Connect() to take effect in the websocket connection.
  265. // If you want to change the proxy after connecting, you must call Disconnect() and then Connect() again manually.
  266. //
  267. // By default, the client will find the proxy from the https_proxy environment variable like Go's net/http does.
  268. //
  269. // To disable reading proxy info from environment variables, explicitly set the proxy to nil:
  270. //
  271. // cli.SetProxy(nil)
  272. //
  273. // To use a different proxy for the websocket and media, pass a function that checks the request path or headers:
  274. //
  275. // cli.SetProxy(func(r *http.Request) (*url.URL, error) {
  276. // if r.URL.Host == "web.whatsapp.com" && r.URL.Path == "/ws/chat" {
  277. // return websocketProxyURL, nil
  278. // } else {
  279. // return mediaProxyURL, nil
  280. // }
  281. // })
  282. func (cli *Client) SetProxy(proxy Proxy, opts ...SetProxyOptions) {
  283. var opt SetProxyOptions
  284. if len(opts) > 0 {
  285. opt = opts[0]
  286. }
  287. transport := (http.DefaultTransport.(*http.Transport)).Clone()
  288. transport.Proxy = proxy
  289. cli.setTransport(transport, opt)
  290. }
  291. type SetProxyOptions struct {
  292. // If NoWebsocket is true, the proxy won't be used for the websocket
  293. NoWebsocket bool
  294. // If OnlyLogin is true, the proxy will be used for the pre-login websocket, but not the post-login one
  295. OnlyLogin bool
  296. // If NoMedia is true, the proxy won't be used for media uploads/downloads
  297. NoMedia bool
  298. }
  299. // SetSOCKSProxy sets a SOCKS5 proxy to use for WhatsApp web websocket connections and media uploads/downloads.
  300. //
  301. // Same details as SetProxy apply, but using a different proxy for the websocket and media is not currently supported.
  302. func (cli *Client) SetSOCKSProxy(px proxy.Dialer, opts ...SetProxyOptions) {
  303. var opt SetProxyOptions
  304. if len(opts) > 0 {
  305. opt = opts[0]
  306. }
  307. transport := (http.DefaultTransport.(*http.Transport)).Clone()
  308. pxc := px.(proxy.ContextDialer)
  309. transport.DialContext = pxc.DialContext
  310. cli.setTransport(transport, opt)
  311. }
  312. func (cli *Client) setTransport(transport *http.Transport, opt SetProxyOptions) {
  313. if !opt.NoWebsocket {
  314. cli.preLoginHTTP.Transport = transport
  315. if !opt.OnlyLogin {
  316. cli.websocketHTTP.Transport = transport
  317. }
  318. }
  319. if !opt.NoMedia {
  320. cli.mediaHTTP.Transport = transport
  321. }
  322. }
  323. // SetMediaHTTPClient sets the HTTP client used to download media.
  324. // This will overwrite any set proxy calls.
  325. func (cli *Client) SetMediaHTTPClient(h *http.Client) {
  326. cli.mediaHTTP = h
  327. }
  328. // SetWebsocketHTTPClient sets the HTTP client used to establish the websocket connection for logged-in sessions.
  329. // This will overwrite any set proxy calls.
  330. func (cli *Client) SetWebsocketHTTPClient(h *http.Client) {
  331. cli.websocketHTTP = h
  332. }
  333. // SetPreLoginHTTPClient sets the HTTP client used to establish the websocket connection before login.
  334. // This will overwrite any set proxy calls.
  335. func (cli *Client) SetPreLoginHTTPClient(h *http.Client) {
  336. cli.preLoginHTTP = h
  337. }
  338. func (cli *Client) getSocketWaitChan() <-chan struct{} {
  339. cli.socketLock.RLock()
  340. ch := cli.socketWait
  341. cli.socketLock.RUnlock()
  342. return ch
  343. }
  344. func (cli *Client) closeSocketWaitChan() {
  345. cli.socketLock.Lock()
  346. close(cli.socketWait)
  347. cli.socketWait = make(chan struct{})
  348. cli.socketLock.Unlock()
  349. }
  350. func (cli *Client) getOwnID() types.JID {
  351. if cli == nil {
  352. return types.EmptyJID
  353. }
  354. return cli.Store.GetJID()
  355. }
  356. func (cli *Client) getOwnLID() types.JID {
  357. if cli == nil {
  358. return types.EmptyJID
  359. }
  360. return cli.Store.GetLID()
  361. }
  362. func (cli *Client) MobileLogin(loginDto *app.LoginDto) error {
  363. cli.Log.Debugf("client MobileLogin")
  364. cli.Disconnect()
  365. //loginDto := &app.LoginDto{}
  366. /* err := json.Unmarshal([]byte(jsonStr), &loginDto)
  367. if err != nil {
  368. return fmt.Errorf("JSON unmarshal error:", err)
  369. }*/
  370. // loginDto.Init()
  371. if loginDto.ClientStaticKeypair != "" {
  372. piarKey, _ := base64.StdEncoding.DecodeString(loginDto.ClientStaticKeypair)
  373. loginDto.StaticPriKey = base64.StdEncoding.EncodeToString(piarKey[:32])
  374. loginDto.StaticPubKey = base64.StdEncoding.EncodeToString(piarKey[32:])
  375. }
  376. if loginDto.StaticPriKey == "" || loginDto.StaticPubKey == "" {
  377. return fmt.Errorf("IncompleteParametersCode StaticPriKey StaticPubKey")
  378. }
  379. info := app.EmptyAccountInfo()
  380. if loginDto.AuthBody == nil && app.IsEmpty(loginDto.AuthHexData) {
  381. return fmt.Errorf("IncompleteParametersCode AuthBody AuthHexData")
  382. } else if loginDto.AuthBody != nil {
  383. err := app.GenAuthDataService(loginDto.AuthBodyActual.ClientPayload)
  384. if err != nil {
  385. return fmt.Errorf("IncompleteParametersCode AuthBody AuthHexData")
  386. }
  387. info.SetCliPayload(loginDto.AuthBodyActual.ClientPayload)
  388. } else if !app.IsEmpty(loginDto.AuthHexData) {
  389. // decode hex
  390. authData, err := hex.DecodeString(loginDto.AuthHexData)
  391. if err != nil {
  392. return fmt.Errorf("ParameterError AuthHexData")
  393. }
  394. // set client payload Data pb
  395. err = info.SetCliPayloadData(authData)
  396. if err != nil {
  397. return fmt.Errorf("ParameterError AuthHexData")
  398. }
  399. }
  400. if loginDto.EdgeRouting != "" {
  401. routingInfo, _ := base64.StdEncoding.DecodeString(loginDto.EdgeRouting)
  402. info.SetRoutingInfo(routingInfo)
  403. }
  404. if loginDto.IdentityPriKey != "" {
  405. _ = info.SetStaticPriKey(loginDto.IdentityPriKey)
  406. }
  407. if loginDto.IdentityPubKey != "" {
  408. _ = info.SetStaticPubKey(loginDto.IdentityPubKey)
  409. }
  410. // set client static secret key
  411. err := info.SetStaticHdBase64Keys(loginDto.StaticPriKey, loginDto.StaticPubKey)
  412. if err != nil {
  413. return fmt.Errorf("StaticPriKey or StaticPubKey")
  414. }
  415. //是否需要var deviceIdentityContainer waProto.ADVSignedDeviceIdentityHMAC ?
  416. cli.Store.Mobile = true
  417. cli.Store.Uuid = loginDto.UUID
  418. cli.Store.ID = &types.JID{User: info.GetUserName(), Server: types.DefaultUserServer}
  419. clientPayload := info.GetClientPayload()
  420. cli.Store.MobileInfo, err = json.Marshal(clientPayload)
  421. if err != nil {
  422. return fmt.Errorf("Marshal clientPayload failed: %w", err)
  423. }
  424. cli.Store.ClientPayload = clientPayload
  425. cli.Store.Platform = clientPayload.UserAgent.GetPlatform().String()
  426. cli.Store.PushName = *clientPayload.PushName
  427. cli.Store.NoiseKey.Pub = (*[32]byte)(info.GetStaticKeys().Public[:])
  428. pri := *(*[32]byte)(info.GetStaticKeys().Private)
  429. cli.Store.NoiseKey.Priv = (*[32]byte)(pri[:])
  430. cli.Store.IdentityKey = &keys.KeyPair{Priv: info.GetStaticPriKey(), Pub: info.GetStaticPubKey()}
  431. cli.Store.SignedPreKey = cli.Store.IdentityKey.CreateSignedPreKey(1)
  432. return cli.Connect()
  433. }
  434. func (cli *Client) WaitForConnection(timeout time.Duration) bool {
  435. if cli == nil {
  436. return false
  437. }
  438. timeoutChan := time.After(timeout)
  439. cli.socketLock.RLock()
  440. for cli.socket == nil || !cli.socket.IsConnected() || !cli.IsLoggedIn() {
  441. ch := cli.socketWait
  442. cli.socketLock.RUnlock()
  443. select {
  444. case <-ch:
  445. case <-timeoutChan:
  446. return false
  447. case <-cli.expectedDisconnect.GetChan():
  448. return false
  449. }
  450. cli.socketLock.RLock()
  451. }
  452. cli.socketLock.RUnlock()
  453. return true
  454. }
  455. // Connect connects the client to the WhatsApp web websocket. After connection, it will either
  456. // authenticate if there's data in the device store, or emit a QREvent to set up a new link.
  457. func (cli *Client) Connect() error {
  458. return cli.ConnectContext(cli.BackgroundEventCtx)
  459. }
  460. func isRetryableConnectError(err error) bool {
  461. if exhttp.IsNetworkError(err) {
  462. return true
  463. }
  464. var statusErr socket.ErrWithStatusCode
  465. if errors.As(err, &statusErr) {
  466. switch statusErr.StatusCode {
  467. case 408, 500, 501, 502, 503, 504:
  468. return true
  469. }
  470. }
  471. return false
  472. }
  473. func (cli *Client) ConnectContext(ctx context.Context) error {
  474. if cli == nil {
  475. return ErrClientIsNil
  476. }
  477. cli.socketLock.Lock()
  478. defer cli.socketLock.Unlock()
  479. err := cli.unlockedConnect(ctx)
  480. if isRetryableConnectError(err) && cli.InitialAutoReconnect && cli.EnableAutoReconnect {
  481. cli.Log.Errorf("Initial connection failed but reconnecting in background (%v)", err)
  482. go cli.dispatchEvent(&events.Disconnected{})
  483. go cli.autoReconnect(ctx)
  484. return nil
  485. }
  486. return err
  487. }
  488. func (cli *Client) connect(ctx context.Context) error {
  489. cli.socketLock.Lock()
  490. defer cli.socketLock.Unlock()
  491. return cli.unlockedConnect(ctx)
  492. }
  493. func (cli *Client) unlockedConnect(ctx context.Context) error {
  494. if cli.socket != nil {
  495. if !cli.socket.IsConnected() {
  496. cli.unlockedDisconnect()
  497. } else {
  498. return ErrAlreadyConnected
  499. }
  500. }
  501. cli.resetExpectedDisconnect()
  502. client := cli.websocketHTTP
  503. if cli.Store.ID == nil {
  504. client = cli.preLoginHTTP
  505. }
  506. fs := socket.NewFrameSocket(cli.Log.Sub("Socket"), client)
  507. if cli.MessengerConfig != nil {
  508. fs.URL = cli.MessengerConfig.WebsocketURL
  509. fs.HTTPHeaders.Set("Origin", cli.MessengerConfig.BaseURL)
  510. fs.HTTPHeaders.Set("User-Agent", cli.MessengerConfig.UserAgent)
  511. fs.HTTPHeaders.Set("Cache-Control", "no-cache")
  512. fs.HTTPHeaders.Set("Pragma", "no-cache")
  513. //fs.HTTPHeaders.Set("Sec-Fetch-Dest", "empty")
  514. //fs.HTTPHeaders.Set("Sec-Fetch-Mode", "websocket")
  515. //fs.HTTPHeaders.Set("Sec-Fetch-Site", "cross-site")
  516. }
  517. if err := fs.Connect(ctx); err != nil {
  518. fs.Close(0)
  519. return err
  520. } else if err = cli.doHandshake(ctx, fs, *keys.NewKeyPair()); err != nil {
  521. fs.Close(0)
  522. return fmt.Errorf("noise handshake failed: %w", err)
  523. }
  524. go cli.keepAliveLoop(ctx, fs.Context())
  525. go cli.handlerQueueLoop(ctx, fs.Context())
  526. return nil
  527. }
  528. // IsLoggedIn returns true after the client is successfully connected and authenticated on WhatsApp.
  529. func (cli *Client) IsLoggedIn() bool {
  530. return cli != nil && cli.isLoggedIn.Load()
  531. }
  532. func (cli *Client) onDisconnect(ctx context.Context, ns *socket.NoiseSocket, remote bool) {
  533. ns.Stop(false)
  534. cli.socketLock.Lock()
  535. defer cli.socketLock.Unlock()
  536. if cli.socket == ns {
  537. cli.socket = nil
  538. cli.clearResponseWaiters(xmlStreamEndNode)
  539. if !cli.isExpectedDisconnect() && remote {
  540. cli.Log.Debugf("Emitting Disconnected event")
  541. go cli.dispatchEvent(&events.Disconnected{})
  542. go cli.autoReconnect(ctx)
  543. } else if remote {
  544. cli.Log.Debugf("OnDisconnect() called, but it was expected, so not emitting event")
  545. } else {
  546. cli.Log.Debugf("OnDisconnect() called after manual disconnection")
  547. }
  548. } else {
  549. cli.Log.Debugf("Ignoring OnDisconnect on different socket")
  550. }
  551. }
  552. func (cli *Client) expectDisconnect() {
  553. cli.expectedDisconnect.Set()
  554. }
  555. func (cli *Client) resetExpectedDisconnect() {
  556. cli.expectedDisconnect.Clear()
  557. }
  558. func (cli *Client) isExpectedDisconnect() bool {
  559. return cli.expectedDisconnect.IsSet()
  560. }
  561. func (cli *Client) autoReconnect(ctx context.Context) {
  562. if !cli.EnableAutoReconnect || cli.Store.ID == nil {
  563. return
  564. }
  565. for {
  566. autoReconnectDelay := time.Duration(cli.AutoReconnectErrors) * 2 * time.Second
  567. cli.Log.Debugf("Automatically reconnecting after %v", autoReconnectDelay)
  568. cli.AutoReconnectErrors++
  569. if cli.expectedDisconnect.WaitTimeoutCtx(ctx, autoReconnectDelay) == nil {
  570. cli.Log.Debugf("Cancelling automatic reconnect due to expected disconnect")
  571. return
  572. } else if ctx.Err() != nil {
  573. cli.Log.Debugf("Cancelling automatic reconnect due to context cancellation")
  574. return
  575. }
  576. err := cli.connect(ctx)
  577. if errors.Is(err, ErrAlreadyConnected) {
  578. cli.Log.Debugf("Connect() said we're already connected after autoreconnect sleep")
  579. return
  580. } else if err != nil {
  581. if cli.expectedDisconnect.IsSet() {
  582. cli.Log.Debugf("Autoreconnect failed, but disconnect was expected, not reconnecting")
  583. return
  584. }
  585. cli.Log.Errorf("Error reconnecting after autoreconnect sleep: %v", err)
  586. if cli.AutoReconnectHook != nil && !cli.AutoReconnectHook(err) {
  587. cli.Log.Debugf("AutoReconnectHook returned false, not reconnecting")
  588. return
  589. }
  590. } else {
  591. return
  592. }
  593. }
  594. }
  595. // IsConnected checks if the client is connected to the WhatsApp web websocket.
  596. // Note that this doesn't check if the client is authenticated. See the IsLoggedIn field for that.
  597. func (cli *Client) IsConnected() bool {
  598. if cli == nil {
  599. return false
  600. }
  601. cli.socketLock.RLock()
  602. connected := cli.socket != nil && cli.socket.IsConnected()
  603. cli.socketLock.RUnlock()
  604. return connected
  605. }
  606. // Disconnect disconnects from the WhatsApp web websocket.
  607. //
  608. // This will not emit any events, the Disconnected event is only used when the
  609. // connection is closed by the server or a network error.
  610. func (cli *Client) Disconnect() {
  611. if cli == nil {
  612. return
  613. }
  614. cli.socketLock.Lock()
  615. cli.expectDisconnect()
  616. cli.unlockedDisconnect()
  617. cli.socketLock.Unlock()
  618. cli.clearDelayedMessageRequests()
  619. }
  620. // Disconnect closes the websocket connection.
  621. func (cli *Client) unlockedDisconnect() {
  622. if cli.socket != nil {
  623. cli.socket.Stop(true)
  624. cli.socket = nil
  625. cli.clearResponseWaiters(xmlStreamEndNode)
  626. }
  627. }
  628. // Logout sends a request to unlink the device, then disconnects from the websocket and deletes the local device store.
  629. //
  630. // If the logout request fails, the disconnection and local data deletion will not happen either.
  631. // If an error is returned, but you want to force disconnect/clear data, call Client.Disconnect() and Client.Store.Delete() manually.
  632. //
  633. // Note that this will not emit any events. The LoggedOut event is only used for external logouts
  634. // (triggered by the user from the main device or by WhatsApp servers).
  635. func (cli *Client) Logout(ctx context.Context) error {
  636. if cli == nil {
  637. return ErrClientIsNil
  638. } else if cli.MessengerConfig != nil {
  639. return errors.New("can't logout with Messenger credentials")
  640. }
  641. ownID := cli.getOwnID()
  642. if ownID.IsEmpty() {
  643. return ErrNotLoggedIn
  644. }
  645. if cli.Store.Mobile {
  646. cli.Disconnect()
  647. err := cli.Store.Delete(ctx)
  648. if err != nil {
  649. return fmt.Errorf("error deleting data from store: %w", err)
  650. }
  651. return nil
  652. }
  653. _, err := cli.sendIQ(ctx, infoQuery{
  654. Namespace: "md",
  655. Type: "set",
  656. To: types.ServerJID,
  657. Content: []waBinary.Node{{
  658. Tag: "remove-companion-device",
  659. Attrs: waBinary.Attrs{
  660. "jid": ownID,
  661. "reason": "user_initiated",
  662. },
  663. }},
  664. })
  665. if err != nil {
  666. return fmt.Errorf("error sending logout request: %w", err)
  667. }
  668. cli.Disconnect()
  669. err = cli.Store.Delete(ctx)
  670. if err != nil {
  671. return fmt.Errorf("error deleting data from store: %w", err)
  672. }
  673. return nil
  674. }
  675. // AddEventHandler registers a new function to receive all events emitted by this client.
  676. //
  677. // The returned integer is the event handler ID, which can be passed to RemoveEventHandler to remove it.
  678. //
  679. // All registered event handlers will receive all events. You should use a type switch statement to
  680. // filter the events you want:
  681. //
  682. // func myEventHandler(evt interface{}) {
  683. // switch v := evt.(type) {
  684. // case *events.Message:
  685. // fmt.Println("Received a message!")
  686. // case *events.Receipt:
  687. // fmt.Println("Received a receipt!")
  688. // }
  689. // }
  690. //
  691. // If you want to access the Client instance inside the event handler, the recommended way is to
  692. // wrap the whole handler in another struct:
  693. //
  694. // type MyClient struct {
  695. // WAClient *whatsmeow.Client
  696. // eventHandlerID uint32
  697. // }
  698. //
  699. // func (mycli *MyClient) register() {
  700. // mycli.eventHandlerID = mycli.WAClient.AddEventHandler(mycli.myEventHandler)
  701. // }
  702. //
  703. // func (mycli *MyClient) myEventHandler(evt interface{}) {
  704. // // Handle event and access mycli.WAClient
  705. // }
  706. func (cli *Client) AddEventHandler(handler EventHandler) uint32 {
  707. return cli.AddEventHandlerWithSuccessStatus(func(evt any) bool {
  708. handler(evt)
  709. return true
  710. })
  711. }
  712. func (cli *Client) AddEventHandlerWithSuccessStatus(handler EventHandlerWithSuccessStatus) uint32 {
  713. nextID := atomic.AddUint32(&nextHandlerID, 1)
  714. cli.eventHandlersLock.Lock()
  715. cli.eventHandlers = append(cli.eventHandlers, wrappedEventHandler{handler, nextID})
  716. cli.eventHandlersLock.Unlock()
  717. return nextID
  718. }
  719. // RemoveEventHandler removes a previously registered event handler function.
  720. // If the function with the given ID is found, this returns true.
  721. //
  722. // N.B. Do not run this directly from an event handler. That would cause a deadlock because the
  723. // event dispatcher holds a read lock on the event handler list, and this method wants a write lock
  724. // on the same list. Instead run it in a goroutine:
  725. //
  726. // func (mycli *MyClient) myEventHandler(evt interface{}) {
  727. // if noLongerWantEvents {
  728. // go mycli.WAClient.RemoveEventHandler(mycli.eventHandlerID)
  729. // }
  730. // }
  731. func (cli *Client) RemoveEventHandler(id uint32) bool {
  732. cli.eventHandlersLock.Lock()
  733. defer cli.eventHandlersLock.Unlock()
  734. for index := range cli.eventHandlers {
  735. if cli.eventHandlers[index].id == id {
  736. if index == 0 {
  737. cli.eventHandlers[0].fn = nil
  738. cli.eventHandlers = cli.eventHandlers[1:]
  739. return true
  740. } else if index < len(cli.eventHandlers)-1 {
  741. copy(cli.eventHandlers[index:], cli.eventHandlers[index+1:])
  742. }
  743. cli.eventHandlers[len(cli.eventHandlers)-1].fn = nil
  744. cli.eventHandlers = cli.eventHandlers[:len(cli.eventHandlers)-1]
  745. return true
  746. }
  747. }
  748. return false
  749. }
  750. // RemoveEventHandlers removes all event handlers that have been registered with AddEventHandler
  751. func (cli *Client) RemoveEventHandlers() {
  752. cli.eventHandlersLock.Lock()
  753. cli.eventHandlers = make([]wrappedEventHandler, 0, 1)
  754. cli.eventHandlersLock.Unlock()
  755. }
  756. func (cli *Client) handleFrame(ctx context.Context, data []byte) {
  757. decompressed, err := waBinary.Unpack(data)
  758. if err != nil {
  759. cli.Log.Warnf("Failed to decompress frame: %v", err)
  760. cli.Log.Debugf("Errored frame hex: %s", hex.EncodeToString(data))
  761. return
  762. }
  763. node, err := waBinary.Unmarshal(decompressed)
  764. if err != nil {
  765. cli.Log.Warnf("Failed to decode node in frame: %v", err)
  766. cli.Log.Debugf("Errored frame hex: %s", hex.EncodeToString(decompressed))
  767. return
  768. }
  769. cli.recvLog.Debugf("%s", node.XMLString())
  770. if node.Tag == "xmlstreamend" {
  771. if !cli.isExpectedDisconnect() {
  772. cli.Log.Warnf("Received stream end frame")
  773. }
  774. // TODO should we do something else?
  775. } else if cli.receiveResponse(ctx, node) {
  776. // handled
  777. } else if _, ok := cli.nodeHandlers[node.Tag]; ok {
  778. select {
  779. case cli.handlerQueue <- node:
  780. case <-ctx.Done():
  781. default:
  782. cli.Log.Warnf("Handler queue is full, message ordering is no longer guaranteed")
  783. go func() {
  784. select {
  785. case cli.handlerQueue <- node:
  786. case <-ctx.Done():
  787. }
  788. }()
  789. }
  790. } else if node.Tag != "ack" {
  791. cli.Log.Debugf("Didn't handle WhatsApp node %s", node.Tag)
  792. }
  793. }
  794. func (cli *Client) handlerQueueLoop(evtCtx, connCtx context.Context) {
  795. ticker := time.NewTicker(30 * time.Second)
  796. ticker.Stop()
  797. cli.Log.Debugf("Starting handler queue loop")
  798. Loop:
  799. for {
  800. select {
  801. case node := <-cli.handlerQueue:
  802. doneChan := make(chan struct{}, 1)
  803. start := time.Now()
  804. go func() {
  805. cli.nodeHandlers[node.Tag](evtCtx, node)
  806. duration := time.Since(start)
  807. doneChan <- struct{}{}
  808. if duration > 5*time.Second {
  809. cli.Log.Warnf("Node handling took %s for %s", duration, node.XMLString())
  810. }
  811. }()
  812. ticker.Reset(30 * time.Second)
  813. for i := 0; i < 10; i++ {
  814. select {
  815. case <-doneChan:
  816. ticker.Stop()
  817. continue Loop
  818. case <-ticker.C:
  819. cli.Log.Warnf("Node handling is taking long for %s (started %s ago)", node.XMLString(), time.Since(start))
  820. }
  821. }
  822. cli.Log.Warnf("Continuing handling of %s in background as it's taking too long", node.XMLString())
  823. ticker.Stop()
  824. case <-connCtx.Done():
  825. cli.Log.Debugf("Closing handler queue loop")
  826. return
  827. }
  828. }
  829. }
  830. func (cli *Client) sendNodeAndGetData(ctx context.Context, node waBinary.Node) ([]byte, error) {
  831. if cli == nil {
  832. return nil, ErrClientIsNil
  833. }
  834. cli.socketLock.RLock()
  835. sock := cli.socket
  836. cli.socketLock.RUnlock()
  837. if sock == nil {
  838. return nil, ErrNotConnected
  839. }
  840. payload, err := waBinary.Marshal(node)
  841. if err != nil {
  842. return nil, fmt.Errorf("failed to marshal node: %w", err)
  843. }
  844. cli.sendLog.Debugf("%s", node.XMLString())
  845. return payload, sock.SendFrame(ctx, payload)
  846. }
  847. func (cli *Client) sendNode(ctx context.Context, node waBinary.Node) error {
  848. _, err := cli.sendNodeAndGetData(ctx, node)
  849. return err
  850. }
  851. func (cli *Client) dispatchEvent(evt any) (handlerFailed bool) {
  852. cli.eventHandlersLock.RLock()
  853. defer func() {
  854. cli.eventHandlersLock.RUnlock()
  855. err := recover()
  856. if err != nil {
  857. cli.Log.Errorf("Event handler panicked while handling a %T: %v\n%s", evt, err, debug.Stack())
  858. }
  859. }()
  860. for _, handler := range cli.eventHandlers {
  861. if !handler.fn(evt) {
  862. return true
  863. }
  864. }
  865. return false
  866. }
  867. // ParseWebMessage parses a WebMessageInfo object into *events.Message to match what real-time messages have.
  868. //
  869. // The chat JID can be found in the Conversation data:
  870. //
  871. // chatJID, err := types.ParseJID(conv.GetId())
  872. // for _, historyMsg := range conv.GetMessages() {
  873. // evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
  874. // yourNormalEventHandler(evt)
  875. // }
  876. func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waWeb.WebMessageInfo) (*events.Message, error) {
  877. var err error
  878. if chatJID.IsEmpty() {
  879. chatJID, err = types.ParseJID(webMsg.GetKey().GetRemoteJID())
  880. if err != nil {
  881. return nil, fmt.Errorf("no chat JID provided and failed to parse remote JID: %w", err)
  882. }
  883. }
  884. info := types.MessageInfo{
  885. MessageSource: types.MessageSource{
  886. Chat: chatJID,
  887. IsFromMe: webMsg.GetKey().GetFromMe(),
  888. IsGroup: chatJID.Server == types.GroupServer,
  889. },
  890. ID: webMsg.GetKey().GetID(),
  891. PushName: webMsg.GetPushName(),
  892. Timestamp: time.Unix(int64(webMsg.GetMessageTimestamp()), 0),
  893. }
  894. if info.IsFromMe {
  895. info.Sender = cli.getOwnID().ToNonAD()
  896. if info.Sender.IsEmpty() {
  897. return nil, ErrNotLoggedIn
  898. }
  899. } else if chatJID.Server == types.DefaultUserServer || chatJID.Server == types.HiddenUserServer || chatJID.Server == types.NewsletterServer {
  900. info.Sender = chatJID
  901. } else if webMsg.GetParticipant() != "" {
  902. info.Sender, err = types.ParseJID(webMsg.GetParticipant())
  903. } else if webMsg.GetKey().GetParticipant() != "" {
  904. info.Sender, err = types.ParseJID(webMsg.GetKey().GetParticipant())
  905. } else {
  906. return nil, fmt.Errorf("couldn't find sender of message %s", info.ID)
  907. }
  908. if err != nil {
  909. return nil, fmt.Errorf("failed to parse sender of message %s: %v", info.ID, err)
  910. }
  911. if pk := webMsg.GetCommentMetadata().GetCommentParentKey(); pk != nil {
  912. info.MsgMetaInfo.ThreadMessageID = pk.GetID()
  913. info.MsgMetaInfo.ThreadMessageSenderJID, _ = types.ParseJID(pk.GetParticipant())
  914. }
  915. evt := &events.Message{
  916. RawMessage: webMsg.GetMessage(),
  917. SourceWebMsg: webMsg,
  918. Info: info,
  919. }
  920. evt.UnwrapRaw()
  921. if evt.Message.GetProtocolMessage().GetType() == waE2E.ProtocolMessage_MESSAGE_EDIT {
  922. evt.Info.ID = evt.Message.GetProtocolMessage().GetKey().GetID()
  923. evt.Message = evt.Message.GetProtocolMessage().GetEditedMessage()
  924. }
  925. return evt, nil
  926. }
  927. func (cli *Client) StoreLIDPNMapping(ctx context.Context, first, second types.JID) {
  928. var lid, pn types.JID
  929. if first.Server == types.HiddenUserServer && second.Server == types.DefaultUserServer {
  930. lid = first
  931. pn = second
  932. } else if first.Server == types.DefaultUserServer && second.Server == types.HiddenUserServer {
  933. lid = second
  934. pn = first
  935. } else {
  936. return
  937. }
  938. err := cli.Store.LIDs.PutLIDMapping(ctx, lid, pn)
  939. if err != nil {
  940. cli.Log.Errorf("Failed to store LID-PN mapping for %s -> %s: %v", lid, pn, err)
  941. }
  942. }