conn.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //Package whatsapp provides a developer API to interact with the WhatsAppWeb-Servers.
  2. package whatsapp
  3. import (
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "encoding/json"
  7. "fmt"
  8. "math/rand"
  9. "net/http"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. "github.com/Rhymen/go-whatsapp/binary"
  16. "github.com/Rhymen/go-whatsapp/crypto/cbc"
  17. "github.com/gorilla/websocket"
  18. )
  19. type metric byte
  20. const (
  21. debugLog metric = iota + 1
  22. queryResume
  23. queryReceipt
  24. queryMedia
  25. queryChat
  26. queryContacts
  27. queryMessages
  28. presence
  29. presenceSubscribe
  30. group
  31. read
  32. chat
  33. received
  34. pic
  35. status
  36. message
  37. queryActions
  38. block
  39. queryGroup
  40. queryPreview
  41. queryEmoji
  42. queryMessageInfo
  43. spam
  44. querySearch
  45. queryIdentity
  46. queryUrl
  47. profile
  48. contact
  49. queryVcard
  50. queryStatus
  51. queryStatusUpdate
  52. privacyStatus
  53. queryLiveLocations
  54. liveLocation
  55. queryVname
  56. queryLabels
  57. call
  58. queryCall
  59. queryQuickReplies
  60. )
  61. type flag byte
  62. const (
  63. ignore flag = 1 << (7 - iota)
  64. ackRequest
  65. available
  66. notAvailable
  67. expires
  68. skipOffline
  69. )
  70. /*
  71. Conn is created by NewConn. Interacting with the initialized Conn is the main way of interacting with our package.
  72. It holds all necessary information to make the package work internally.
  73. */
  74. type Conn struct {
  75. wsConn *websocket.Conn
  76. wsConnOK bool
  77. wsConnMutex sync.RWMutex
  78. session *Session
  79. listener map[string]chan string
  80. listenerMutex sync.RWMutex
  81. writeChan chan wsMsg
  82. handler []Handler
  83. msgCount int
  84. msgTimeout time.Duration
  85. Info *Info
  86. Store *Store
  87. ServerLastSeen time.Time
  88. longClientName string
  89. shortClientName string
  90. }
  91. type wsMsg struct {
  92. messageType int
  93. data []byte
  94. }
  95. /*
  96. Creates a new connection with a given timeout. The websocket connection to the WhatsAppWeb servers get´s established.
  97. The goroutine for handling incoming messages is started
  98. */
  99. func NewConn(timeout time.Duration) (*Conn, error) {
  100. wac := &Conn{
  101. wsConn: nil, // will be set in connect()
  102. wsConnMutex: sync.RWMutex{},
  103. listener: make(map[string]chan string),
  104. listenerMutex: sync.RWMutex{},
  105. writeChan: make(chan wsMsg),
  106. handler: make([]Handler, 0),
  107. msgCount: 0,
  108. msgTimeout: timeout,
  109. Store: newStore(),
  110. longClientName: "github.com/rhymen/go-whatsapp",
  111. shortClientName: "go-whatsapp",
  112. }
  113. if err := wac.connect(); err != nil {
  114. return nil, err
  115. }
  116. go wac.readPump()
  117. go wac.writePump()
  118. go wac.keepAlive(20000, 90000)
  119. return wac, nil
  120. }
  121. func (wac *Conn) isConnected() bool {
  122. wac.wsConnMutex.RLock()
  123. defer wac.wsConnMutex.RUnlock()
  124. if wac.wsConn == nil {
  125. return false
  126. }
  127. if wac.wsConnOK {
  128. return true
  129. }
  130. // just send a keepalive to test the connection
  131. wac.sendKeepAlive()
  132. // this method is expected to be called by loops. So we can just return false
  133. return false
  134. }
  135. // connect should be guarded with wsConnMutex
  136. func (wac *Conn) connect() error {
  137. dialer := &websocket.Dialer{
  138. ReadBufferSize: 25 * 1024 * 1024,
  139. WriteBufferSize: 10 * 1024 * 1024,
  140. HandshakeTimeout: wac.msgTimeout,
  141. }
  142. headers := http.Header{"Origin": []string{"https://web.whatsapp.com"}}
  143. wsConn, _, err := dialer.Dial("wss://w3.web.whatsapp.com/ws", headers)
  144. if err != nil {
  145. return fmt.Errorf("couldn't dial whatsapp web websocket: %v", err)
  146. }
  147. wsConn.SetCloseHandler(func(code int, text string) error {
  148. fmt.Fprintf(os.Stderr, "websocket connection closed(%d, %s)\n", code, text)
  149. // from default CloseHandler
  150. message := websocket.FormatCloseMessage(code, "")
  151. wsConn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
  152. // our close handling
  153. if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
  154. fmt.Println("Trigger reconnect")
  155. go wac.reconnect()
  156. }
  157. return nil
  158. })
  159. wac.wsConn = wsConn
  160. wac.wsConnOK = true
  161. return nil
  162. }
  163. // reconnect should be run as go routine
  164. func (wac *Conn) reconnect() {
  165. wac.wsConnMutex.Lock()
  166. wac.wsConn.Close()
  167. wac.wsConn = nil
  168. wac.wsConnOK = false
  169. wac.wsConnMutex.Unlock()
  170. // wait up to 60 seconds and then reconnect. As writePump should send immediately, it might
  171. // reconnect as well. So we check its existance before reconnecting
  172. for !wac.isConnected() {
  173. time.Sleep(time.Duration(rand.Intn(60)) * time.Second)
  174. wac.wsConnMutex.Lock()
  175. if wac.wsConn == nil {
  176. if err := wac.connect(); err != nil {
  177. fmt.Fprintf(os.Stderr, "could not reconnect to websocket: %v\n", err)
  178. }
  179. }
  180. wac.wsConnMutex.Unlock()
  181. }
  182. }
  183. func (wac *Conn) write(data []interface{}) (<-chan string, error) {
  184. d, err := json.Marshal(data)
  185. if err != nil {
  186. return nil, err
  187. }
  188. ts := time.Now().Unix()
  189. messageTag := fmt.Sprintf("%d.--%d", ts, wac.msgCount)
  190. msg := fmt.Sprintf("%s,%s", messageTag, d)
  191. ch := make(chan string, 1)
  192. wac.listenerMutex.Lock()
  193. wac.listener[messageTag] = ch
  194. wac.listenerMutex.Unlock()
  195. wac.writeChan <- wsMsg{websocket.TextMessage, []byte(msg)}
  196. wac.msgCount++
  197. return ch, nil
  198. }
  199. func (wac *Conn) writeBinary(node binary.Node, metric metric, flag flag, tag string) (<-chan string, error) {
  200. if len(tag) < 2 {
  201. return nil, fmt.Errorf("no tag specified or to short")
  202. }
  203. b, err := binary.Marshal(node)
  204. if err != nil {
  205. return nil, err
  206. }
  207. cipher, err := cbc.Encrypt(wac.session.EncKey, nil, b)
  208. if err != nil {
  209. return nil, err
  210. }
  211. h := hmac.New(sha256.New, wac.session.MacKey)
  212. h.Write(cipher)
  213. hash := h.Sum(nil)
  214. data := []byte(tag + ",")
  215. data = append(data, byte(metric), byte(flag))
  216. data = append(data, hash[:32]...)
  217. data = append(data, cipher...)
  218. ch := make(chan string, 1)
  219. wac.listenerMutex.Lock()
  220. wac.listener[tag] = ch
  221. wac.listenerMutex.Unlock()
  222. msg := wsMsg{websocket.BinaryMessage, data}
  223. wac.writeChan <- msg
  224. wac.msgCount++
  225. return ch, nil
  226. }
  227. func (wac *Conn) readPump() {
  228. defer wac.wsConn.Close()
  229. for {
  230. msgType, msg, err := wac.wsConn.ReadMessage()
  231. if err != nil {
  232. wac.wsConnOK = false
  233. if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
  234. wac.handle(fmt.Errorf("unexpected websocket close: %v", err))
  235. }
  236. // sleep for a second and retry reading the next message
  237. time.Sleep(time.Second)
  238. continue
  239. }
  240. wac.wsConnOK = true
  241. data := strings.SplitN(string(msg), ",", 2)
  242. //Kepp-Alive Timestmap
  243. if data[0][0] == '!' {
  244. msecs, err := strconv.ParseInt(data[0][1:], 10, 64)
  245. if err != nil {
  246. fmt.Fprintf(os.Stderr, "Error converting time string to uint: %v\n", err)
  247. continue
  248. }
  249. wac.ServerLastSeen = time.Unix(msecs/1000, (msecs%1000)*int64(time.Millisecond))
  250. continue
  251. }
  252. wac.listenerMutex.RLock()
  253. listener, hasListener := wac.listener[data[0]]
  254. wac.listenerMutex.RUnlock()
  255. if hasListener && len(data[1]) > 0 {
  256. listener <- data[1]
  257. wac.listenerMutex.Lock()
  258. delete(wac.listener, data[0])
  259. wac.listenerMutex.Unlock()
  260. } else if msgType == 2 && wac.session != nil && wac.session.EncKey != nil {
  261. message, err := wac.decryptBinaryMessage([]byte(data[1]))
  262. if err != nil {
  263. wac.handle(fmt.Errorf("error decoding binary: %v", err))
  264. continue
  265. }
  266. wac.dispatch(message)
  267. } else {
  268. if len(data[1]) > 0 {
  269. wac.handle(string(data[1]))
  270. }
  271. }
  272. }
  273. }
  274. func (wac *Conn) writePump() {
  275. for msg := range wac.writeChan {
  276. for !wac.isConnected() {
  277. // reconnect to send the message ASAP
  278. wac.wsConnMutex.Lock()
  279. if wac.wsConn == nil {
  280. if err := wac.connect(); err != nil {
  281. fmt.Fprintf(os.Stderr, "could not reconnect to websocket: %v\n", err)
  282. }
  283. }
  284. wac.wsConnMutex.Unlock()
  285. if !wac.isConnected() {
  286. // reconnecting failed. Sleep for a while and try again afterwards
  287. time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
  288. }
  289. }
  290. if err := wac.wsConn.WriteMessage(msg.messageType, msg.data); err != nil {
  291. fmt.Fprintf(os.Stderr, "error writing to socket: %v\n", err)
  292. wac.wsConnOK = false
  293. // add message to channel again to no loose it
  294. go func() {
  295. wac.writeChan <- msg
  296. }()
  297. }
  298. }
  299. }
  300. func (wac *Conn) sendKeepAlive() {
  301. // whatever issues might be there allow sending this message
  302. wac.wsConnOK = true
  303. wac.writeChan <- wsMsg{
  304. messageType: websocket.TextMessage,
  305. data: []byte("?,,"),
  306. }
  307. }
  308. func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
  309. for {
  310. wac.sendKeepAlive()
  311. interval := rand.Intn(maxIntervalMs-minIntervalMs) + minIntervalMs
  312. <-time.After(time.Duration(interval) * time.Millisecond)
  313. }
  314. }
  315. func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) {
  316. //message validation
  317. h2 := hmac.New(sha256.New, wac.session.MacKey)
  318. h2.Write([]byte(msg[32:]))
  319. if !hmac.Equal(h2.Sum(nil), msg[:32]) {
  320. return nil, fmt.Errorf("message received with invalid hmac")
  321. }
  322. // message decrypt
  323. d, err := cbc.Decrypt(wac.session.EncKey, nil, msg[32:])
  324. if err != nil {
  325. return nil, fmt.Errorf("error decrypting message with AES: %v", err)
  326. }
  327. // message unmarshal
  328. message, err := binary.Unmarshal(d)
  329. if err != nil {
  330. return nil, fmt.Errorf("error decoding binary: %v", err)
  331. }
  332. return message, nil
  333. }