user.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2020 Tulir Asokan
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "sort"
  21. "strconv"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/pkg/errors"
  26. "github.com/skip2/go-qrcode"
  27. log "maunium.net/go/maulogger/v2"
  28. "maunium.net/go/mautrix"
  29. "github.com/Rhymen/go-whatsapp"
  30. waBinary "github.com/Rhymen/go-whatsapp/binary"
  31. waProto "github.com/Rhymen/go-whatsapp/binary/proto"
  32. "maunium.net/go/mautrix/event"
  33. "maunium.net/go/mautrix/format"
  34. "maunium.net/go/mautrix/id"
  35. "maunium.net/go/mautrix-whatsapp/database"
  36. "maunium.net/go/mautrix-whatsapp/types"
  37. "maunium.net/go/mautrix-whatsapp/whatsapp-ext"
  38. )
  39. type User struct {
  40. *database.User
  41. Conn *whatsappExt.ExtendedConn
  42. bridge *Bridge
  43. log log.Logger
  44. Admin bool
  45. Whitelisted bool
  46. RelaybotWhitelisted bool
  47. IsRelaybot bool
  48. ConnectionErrors int
  49. CommunityID string
  50. cleanDisconnection bool
  51. batteryWarningsSent int
  52. chatListReceived chan struct{}
  53. syncPortalsDone chan struct{}
  54. messages chan PortalMessage
  55. syncStart chan struct{}
  56. syncWait sync.WaitGroup
  57. mgmtCreateLock sync.Mutex
  58. }
  59. func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
  60. _, isPuppet := bridge.ParsePuppetMXID(userID)
  61. if isPuppet || userID == bridge.Bot.UserID {
  62. return nil
  63. }
  64. bridge.usersLock.Lock()
  65. defer bridge.usersLock.Unlock()
  66. user, ok := bridge.usersByMXID[userID]
  67. if !ok {
  68. return bridge.loadDBUser(bridge.DB.User.GetByMXID(userID), &userID)
  69. }
  70. return user
  71. }
  72. func (bridge *Bridge) GetUserByJID(userID types.WhatsAppID) *User {
  73. bridge.usersLock.Lock()
  74. defer bridge.usersLock.Unlock()
  75. user, ok := bridge.usersByJID[userID]
  76. if !ok {
  77. return bridge.loadDBUser(bridge.DB.User.GetByJID(userID), nil)
  78. }
  79. return user
  80. }
  81. func (user *User) addToJIDMap() {
  82. user.bridge.usersLock.Lock()
  83. user.bridge.usersByJID[user.JID] = user
  84. user.bridge.usersLock.Unlock()
  85. }
  86. func (user *User) removeFromJIDMap() {
  87. user.bridge.usersLock.Lock()
  88. delete(user.bridge.usersByJID, user.JID)
  89. user.bridge.usersLock.Unlock()
  90. }
  91. func (bridge *Bridge) GetAllUsers() []*User {
  92. bridge.usersLock.Lock()
  93. defer bridge.usersLock.Unlock()
  94. dbUsers := bridge.DB.User.GetAll()
  95. output := make([]*User, len(dbUsers))
  96. for index, dbUser := range dbUsers {
  97. user, ok := bridge.usersByMXID[dbUser.MXID]
  98. if !ok {
  99. user = bridge.loadDBUser(dbUser, nil)
  100. }
  101. output[index] = user
  102. }
  103. return output
  104. }
  105. func (bridge *Bridge) loadDBUser(dbUser *database.User, mxid *id.UserID) *User {
  106. if dbUser == nil {
  107. if mxid == nil {
  108. return nil
  109. }
  110. dbUser = bridge.DB.User.New()
  111. dbUser.MXID = *mxid
  112. dbUser.Insert()
  113. }
  114. user := bridge.NewUser(dbUser)
  115. bridge.usersByMXID[user.MXID] = user
  116. if len(user.JID) > 0 {
  117. bridge.usersByJID[user.JID] = user
  118. }
  119. if len(user.ManagementRoom) > 0 {
  120. bridge.managementRooms[user.ManagementRoom] = user
  121. }
  122. return user
  123. }
  124. func (user *User) GetPortals() []*Portal {
  125. keys := user.User.GetPortalKeys()
  126. portals := make([]*Portal, len(keys))
  127. user.bridge.portalsLock.Lock()
  128. for i, key := range keys {
  129. portal, ok := user.bridge.portalsByJID[key]
  130. if !ok {
  131. portal = user.bridge.loadDBPortal(user.bridge.DB.Portal.GetByJID(key), &key)
  132. }
  133. portals[i] = portal
  134. }
  135. user.bridge.portalsLock.Unlock()
  136. return portals
  137. }
  138. func (bridge *Bridge) NewUser(dbUser *database.User) *User {
  139. user := &User{
  140. User: dbUser,
  141. bridge: bridge,
  142. log: bridge.Log.Sub("User").Sub(string(dbUser.MXID)),
  143. IsRelaybot: false,
  144. chatListReceived: make(chan struct{}, 1),
  145. syncPortalsDone: make(chan struct{}, 1),
  146. syncStart: make(chan struct{}, 1),
  147. messages: make(chan PortalMessage, bridge.Config.Bridge.UserMessageBuffer),
  148. }
  149. user.RelaybotWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelaybotWhitelisted(user.MXID)
  150. user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
  151. user.Admin = user.bridge.Config.Bridge.Permissions.IsAdmin(user.MXID)
  152. go user.handleMessageLoop()
  153. return user
  154. }
  155. func (user *User) GetManagementRoom() id.RoomID {
  156. if len(user.ManagementRoom) == 0 {
  157. user.mgmtCreateLock.Lock()
  158. defer user.mgmtCreateLock.Unlock()
  159. if len(user.ManagementRoom) > 0 {
  160. return user.ManagementRoom
  161. }
  162. resp, err := user.bridge.Bot.CreateRoom(&mautrix.ReqCreateRoom{
  163. Topic: "WhatsApp bridge notices",
  164. IsDirect: true,
  165. })
  166. if err != nil {
  167. user.log.Errorln("Failed to auto-create management room:", err)
  168. } else {
  169. user.SetManagementRoom(resp.RoomID)
  170. }
  171. }
  172. return user.ManagementRoom
  173. }
  174. func (user *User) SetManagementRoom(roomID id.RoomID) {
  175. existingUser, ok := user.bridge.managementRooms[roomID]
  176. if ok {
  177. existingUser.ManagementRoom = ""
  178. existingUser.Update()
  179. }
  180. user.ManagementRoom = roomID
  181. user.bridge.managementRooms[user.ManagementRoom] = user
  182. user.Update()
  183. }
  184. func (user *User) SetSession(session *whatsapp.Session) {
  185. user.Session = session
  186. if session == nil {
  187. user.LastConnection = 0
  188. }
  189. user.Update()
  190. }
  191. func (user *User) Connect(evenIfNoSession bool) bool {
  192. if user.Conn != nil {
  193. return true
  194. } else if !evenIfNoSession && user.Session == nil {
  195. return false
  196. }
  197. user.log.Debugln("Connecting to WhatsApp")
  198. timeout := time.Duration(user.bridge.Config.Bridge.ConnectionTimeout)
  199. if timeout == 0 {
  200. timeout = 20
  201. }
  202. conn, err := whatsapp.NewConn(timeout * time.Second)
  203. if err != nil {
  204. user.log.Errorln("Failed to connect to WhatsApp:", err)
  205. user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp server. " +
  206. "This indicates a network problem on the bridge server. See bridge logs for more info.")
  207. return false
  208. }
  209. user.Conn = whatsappExt.ExtendConn(conn)
  210. _ = user.Conn.SetClientName(user.bridge.Config.WhatsApp.DeviceName, user.bridge.Config.WhatsApp.ShortName, WAVersion)
  211. user.log.Debugln("WhatsApp connection successful")
  212. user.Conn.AddHandler(user)
  213. return user.RestoreSession()
  214. }
  215. func (user *User) RestoreSession() bool {
  216. if user.Session != nil {
  217. sess, err := user.Conn.RestoreWithSession(*user.Session)
  218. if err == whatsapp.ErrAlreadyLoggedIn {
  219. return true
  220. } else if err != nil {
  221. user.log.Errorln("Failed to restore session:", err)
  222. if errors.Is(err, whatsapp.ErrUnpaired) {
  223. user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " +
  224. "To re-pair your phone, use `delete-session` and then `login`.")
  225. } else {
  226. user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " +
  227. "on your phone is reachable and use `reconnect` to try connecting again.")
  228. }
  229. user.log.Debugln("Disconnecting due to failed session restore...")
  230. _, err := user.Conn.Disconnect()
  231. if err != nil {
  232. user.log.Errorln("Failed to disconnect after failed session restore:", err)
  233. }
  234. return false
  235. }
  236. user.ConnectionErrors = 0
  237. user.SetSession(&sess)
  238. user.log.Debugln("Session restored successfully")
  239. user.PostLogin()
  240. }
  241. return true
  242. }
  243. func (user *User) HasSession() bool {
  244. return user.Session != nil
  245. }
  246. func (user *User) IsConnected() bool {
  247. return user.Conn != nil && user.Conn.IsConnected() && user.Conn.IsLoggedIn()
  248. }
  249. func (user *User) IsLoginInProgress() bool {
  250. return user.Conn != nil && user.Conn.IsLoginInProgress()
  251. }
  252. func (user *User) loginQrChannel(ce *CommandEvent, qrChan <-chan string, eventIDChan chan<- id.EventID) {
  253. var qrEventID id.EventID
  254. for code := range qrChan {
  255. if code == "stop" {
  256. return
  257. }
  258. qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
  259. if err != nil {
  260. user.log.Errorln("Failed to encode QR code:", err)
  261. ce.Reply("Failed to encode QR code: %v", err)
  262. return
  263. }
  264. bot := user.bridge.AS.BotClient()
  265. resp, err := bot.UploadBytes(qrCode, "image/png")
  266. if err != nil {
  267. user.log.Errorln("Failed to upload QR code:", err)
  268. ce.Reply("Failed to upload QR code: %v", err)
  269. return
  270. }
  271. if qrEventID == "" {
  272. sendResp, err := bot.SendImage(ce.RoomID, code, resp.ContentURI)
  273. if err != nil {
  274. user.log.Errorln("Failed to send QR code to user:", err)
  275. return
  276. }
  277. qrEventID = sendResp.EventID
  278. eventIDChan <- qrEventID
  279. } else {
  280. _, err = bot.SendMessageEvent(ce.RoomID, event.EventMessage, &event.MessageEventContent{
  281. MsgType: event.MsgImage,
  282. Body: code,
  283. URL: resp.ContentURI.CUString(),
  284. NewContent: &event.MessageEventContent{
  285. MsgType: event.MsgImage,
  286. Body: code,
  287. URL: resp.ContentURI.CUString(),
  288. },
  289. RelatesTo: &event.RelatesTo{
  290. Type: event.RelReplace,
  291. EventID: qrEventID,
  292. },
  293. })
  294. if err != nil {
  295. user.log.Errorln("Failed to send edited QR code to user:", err)
  296. }
  297. }
  298. }
  299. }
  300. func (user *User) Login(ce *CommandEvent) {
  301. qrChan := make(chan string, 3)
  302. eventIDChan := make(chan id.EventID, 1)
  303. go user.loginQrChannel(ce, qrChan, eventIDChan)
  304. session, err := user.Conn.LoginWithRetry(qrChan, user.bridge.Config.Bridge.LoginQRRegenCount)
  305. qrChan <- "stop"
  306. if err != nil {
  307. var eventID id.EventID
  308. select {
  309. case eventID = <-eventIDChan:
  310. default:
  311. }
  312. reply := event.MessageEventContent{
  313. MsgType: event.MsgText,
  314. }
  315. if err == whatsapp.ErrAlreadyLoggedIn {
  316. reply.Body = "You're already logged in"
  317. } else if err == whatsapp.ErrLoginInProgress {
  318. reply.Body = "You have a login in progress already."
  319. } else if err == whatsapp.ErrLoginTimedOut {
  320. reply.Body = "QR code scan timed out. Please try again."
  321. } else {
  322. user.log.Warnln("Failed to log in:", err)
  323. reply.Body = fmt.Sprintf("Unknown error while logging in: %v", err)
  324. }
  325. msg := reply
  326. if eventID != "" {
  327. msg.NewContent = &reply
  328. msg.RelatesTo = &event.RelatesTo{
  329. Type: event.RelReplace,
  330. EventID: eventID,
  331. }
  332. }
  333. _, _ = ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &msg)
  334. return
  335. }
  336. // TODO there's a bit of duplication between this and the provisioning API login method
  337. // Also between the two logout methods (commands.go and provisioning.go)
  338. user.ConnectionErrors = 0
  339. user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1)
  340. user.addToJIDMap()
  341. user.SetSession(&session)
  342. ce.Reply("Successfully logged in, synchronizing chats...")
  343. user.PostLogin()
  344. }
  345. type Chat struct {
  346. Portal *Portal
  347. LastMessageTime uint64
  348. Contact whatsapp.Contact
  349. }
  350. type ChatList []Chat
  351. func (cl ChatList) Len() int {
  352. return len(cl)
  353. }
  354. func (cl ChatList) Less(i, j int) bool {
  355. return cl[i].LastMessageTime > cl[j].LastMessageTime
  356. }
  357. func (cl ChatList) Swap(i, j int) {
  358. cl[i], cl[j] = cl[j], cl[i]
  359. }
  360. func (user *User) PostLogin() {
  361. user.log.Debugln("Locking processing of incoming messages and starting post-login sync")
  362. user.syncWait.Add(1)
  363. user.syncStart <- struct{}{}
  364. go user.intPostLogin()
  365. }
  366. func (user *User) tryAutomaticDoublePuppeting() {
  367. if len(user.bridge.Config.Bridge.LoginSharedSecret) == 0 {
  368. // Automatic login not enabled
  369. return
  370. } else if _, homeserver, _ := user.MXID.Parse(); homeserver != user.bridge.Config.Homeserver.Domain {
  371. // user is on another homeserver
  372. return
  373. }
  374. puppet := user.bridge.GetPuppetByJID(user.JID)
  375. if len(puppet.CustomMXID) > 0 {
  376. // Custom puppet already enabled
  377. return
  378. }
  379. accessToken, err := puppet.loginWithSharedSecret(user.MXID)
  380. if err != nil {
  381. user.log.Warnln("Failed to login with shared secret:", err)
  382. return
  383. }
  384. err = puppet.SwitchCustomMXID(accessToken, user.MXID)
  385. if err != nil {
  386. puppet.log.Warnln("Failed to switch to auto-logined custom puppet:", err)
  387. return
  388. }
  389. user.log.Infoln("Successfully automatically enabled custom puppet")
  390. }
  391. func (user *User) sendBridgeNotice(formatString string, args ...interface{}) {
  392. notice := fmt.Sprintf(formatString, args...)
  393. _, err := user.bridge.Bot.SendNotice(user.GetManagementRoom(), notice)
  394. if err != nil {
  395. user.log.Warnf("Failed to send bridge notice \"%s\": %v", notice, err)
  396. }
  397. }
  398. func (user *User) sendMarkdownBridgeAlert(formatString string, args ...interface{}) {
  399. notice := fmt.Sprintf(formatString, args...)
  400. content := format.RenderMarkdown(notice, true, false)
  401. _, err := user.bridge.Bot.SendMessageEvent(user.GetManagementRoom(), event.EventMessage, content)
  402. if err != nil {
  403. user.log.Warnf("Failed to send bridge alert \"%s\": %v", notice, err)
  404. }
  405. }
  406. func (user *User) intPostLogin() {
  407. defer user.syncWait.Done()
  408. user.createCommunity()
  409. user.tryAutomaticDoublePuppeting()
  410. err := user.Conn.AdminTest()
  411. if err != nil {
  412. user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err)
  413. sess, disconnectErr := user.Conn.Disconnect()
  414. if disconnectErr != nil {
  415. user.log.Warnln("Error while disconnecting after failed post-login ping:", disconnectErr)
  416. } else {
  417. user.Session = &sess
  418. }
  419. user.bridge.Metrics.TrackDisconnection(user.MXID)
  420. go func() {
  421. time.Sleep(1 * time.Second)
  422. user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
  423. }()
  424. return
  425. } else {
  426. user.log.Debugln("Post-login ping OK")
  427. }
  428. select {
  429. case <-user.chatListReceived:
  430. user.log.Debugln("Chat list receive confirmation received in PostLogin")
  431. case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second):
  432. user.log.Warnln("Timed out waiting for chat list to arrive!")
  433. return
  434. }
  435. select {
  436. case <-user.syncPortalsDone:
  437. user.log.Debugln("Post-login portal sync complete, unlocking processing of incoming messages.")
  438. case <-time.After(time.Duration(user.bridge.Config.Bridge.PortalSyncWait) * time.Second):
  439. user.log.Warnln("Timed out waiting for portal sync to complete! Unlocking processing of incoming messages.")
  440. }
  441. }
  442. func (user *User) HandleChatList(chats []whatsapp.Chat) {
  443. user.log.Infoln("Chat list received")
  444. chatMap := make(map[string]whatsapp.Chat)
  445. for _, chat := range user.Conn.Store.Chats {
  446. chatMap[chat.Jid] = chat
  447. }
  448. for _, chat := range chats {
  449. chatMap[chat.Jid] = chat
  450. }
  451. select {
  452. case user.chatListReceived <- struct{}{}:
  453. default:
  454. }
  455. go user.syncPortals(chatMap, false)
  456. }
  457. func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool) {
  458. if chatMap == nil {
  459. chatMap = user.Conn.Store.Chats
  460. }
  461. user.log.Infoln("Reading chat list")
  462. chats := make(ChatList, 0, len(chatMap))
  463. existingKeys := user.GetInCommunityMap()
  464. portalKeys := make([]database.PortalKeyWithMeta, 0, len(chatMap))
  465. for _, chat := range chatMap {
  466. ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64)
  467. if err != nil {
  468. user.log.Warnfln("Non-integer last message time in %s: %s", chat.Jid, chat.LastMessageTime)
  469. continue
  470. }
  471. portal := user.GetPortalByJID(chat.Jid)
  472. chats = append(chats, Chat{
  473. Portal: portal,
  474. Contact: user.Conn.Store.Contacts[chat.Jid],
  475. LastMessageTime: ts,
  476. })
  477. var inCommunity, ok bool
  478. if inCommunity, ok = existingKeys[portal.Key]; !ok || !inCommunity {
  479. inCommunity = user.addPortalToCommunity(portal)
  480. if portal.IsPrivateChat() {
  481. puppet := user.bridge.GetPuppetByJID(portal.Key.JID)
  482. user.addPuppetToCommunity(puppet)
  483. }
  484. }
  485. portalKeys = append(portalKeys, database.PortalKeyWithMeta{PortalKey: portal.Key, InCommunity: inCommunity})
  486. }
  487. user.log.Infoln("Read chat list, updating user-portal mapping")
  488. err := user.SetPortalKeys(portalKeys)
  489. if err != nil {
  490. user.log.Warnln("Failed to update user-portal mapping:", err)
  491. }
  492. sort.Sort(chats)
  493. limit := user.bridge.Config.Bridge.InitialChatSync
  494. if limit < 0 {
  495. limit = len(chats)
  496. }
  497. now := uint64(time.Now().Unix())
  498. user.log.Infoln("Syncing portals")
  499. for i, chat := range chats {
  500. if chat.LastMessageTime+user.bridge.Config.Bridge.SyncChatMaxAge < now {
  501. break
  502. }
  503. create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit
  504. if len(chat.Portal.MXID) > 0 || create || createAll {
  505. chat.Portal.Sync(user, chat.Contact)
  506. err := chat.Portal.BackfillHistory(user, chat.LastMessageTime)
  507. if err != nil {
  508. chat.Portal.log.Errorln("Error backfilling history:", err)
  509. }
  510. }
  511. }
  512. user.log.Infoln("Finished syncing portals")
  513. select {
  514. case user.syncPortalsDone <- struct{}{}:
  515. default:
  516. }
  517. }
  518. func (user *User) HandleContactList(contacts []whatsapp.Contact) {
  519. contactMap := make(map[string]whatsapp.Contact)
  520. for _, contact := range contacts {
  521. contactMap[contact.Jid] = contact
  522. }
  523. go user.syncPuppets(contactMap)
  524. }
  525. func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) {
  526. if contacts == nil {
  527. contacts = user.Conn.Store.Contacts
  528. }
  529. user.log.Infoln("Syncing puppet info from contacts")
  530. for jid, contact := range contacts {
  531. if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) {
  532. puppet := user.bridge.GetPuppetByJID(contact.Jid)
  533. puppet.Sync(user, contact)
  534. }
  535. }
  536. user.log.Infoln("Finished syncing puppet info from contacts")
  537. }
  538. func (user *User) updateLastConnectionIfNecessary() {
  539. if user.LastConnection+60 < uint64(time.Now().Unix()) {
  540. user.UpdateLastConnection()
  541. }
  542. }
  543. func (user *User) HandleError(err error) {
  544. if errors.Cause(err) != whatsapp.ErrInvalidWsData {
  545. user.log.Errorfln("WhatsApp error: %v", err)
  546. }
  547. if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok {
  548. user.bridge.Metrics.TrackDisconnection(user.MXID)
  549. if closed.Code == 1000 && user.cleanDisconnection {
  550. user.cleanDisconnection = false
  551. user.log.Infoln("Clean disconnection by server")
  552. return
  553. }
  554. go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection was closed with websocket status code %d", closed.Code))
  555. } else if failed, ok := err.(*whatsapp.ErrConnectionFailed); ok {
  556. user.bridge.Metrics.TrackDisconnection(user.MXID)
  557. user.ConnectionErrors++
  558. go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection failed: %v", failed.Err))
  559. }
  560. // Otherwise unknown error, probably mostly harmless
  561. }
  562. func (user *User) tryReconnect(msg string) {
  563. if user.ConnectionErrors > user.bridge.Config.Bridge.MaxConnectionAttempts {
  564. user.sendMarkdownBridgeAlert("%s. Use the `reconnect` command to reconnect.", msg)
  565. return
  566. }
  567. if user.bridge.Config.Bridge.ReportConnectionRetry {
  568. user.sendBridgeNotice("%s. Reconnecting...", msg)
  569. // Don't want the same error to be repeated
  570. msg = ""
  571. }
  572. var tries uint
  573. var exponentialBackoff bool
  574. baseDelay := time.Duration(user.bridge.Config.Bridge.ConnectionRetryDelay)
  575. if baseDelay < 0 {
  576. exponentialBackoff = true
  577. baseDelay = -baseDelay + 1
  578. }
  579. delay := baseDelay
  580. for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
  581. err := user.Conn.Restore()
  582. if err == nil {
  583. user.ConnectionErrors = 0
  584. if user.bridge.Config.Bridge.ReportConnectionRetry {
  585. user.sendBridgeNotice("Reconnected successfully")
  586. }
  587. user.PostLogin()
  588. return
  589. } else if err.Error() == "init responded with 400" {
  590. user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...")
  591. sess, err := user.Conn.Disconnect()
  592. if err != nil {
  593. user.log.Debugln("Error while disconnecting for connection reset:", err)
  594. }
  595. if len(sess.Wid) > 0 {
  596. user.SetSession(&sess)
  597. }
  598. }
  599. user.log.Errorln("Error while trying to reconnect after disconnection:", err)
  600. tries++
  601. user.ConnectionErrors++
  602. if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
  603. if exponentialBackoff {
  604. delay = (1 << tries) + baseDelay
  605. }
  606. if user.bridge.Config.Bridge.ReportConnectionRetry {
  607. user.sendBridgeNotice("Reconnection attempt failed: %v. Retrying in %d seconds...", err, delay)
  608. }
  609. time.Sleep(delay * time.Second)
  610. }
  611. }
  612. if user.bridge.Config.Bridge.ReportConnectionRetry {
  613. user.sendMarkdownBridgeAlert("%d reconnection attempts failed. Use the `reconnect` command to try to reconnect manually.", tries)
  614. } else {
  615. user.sendMarkdownBridgeAlert("\u26a0 %s. Additionally, %d reconnection attempts failed. Use the `reconnect` command to try to reconnect.", msg, tries)
  616. }
  617. }
  618. func (user *User) ShouldCallSynchronously() bool {
  619. return true
  620. }
  621. func (user *User) HandleJSONParseError(err error) {
  622. user.log.Errorln("WhatsApp JSON parse error:", err)
  623. }
  624. func (user *User) PortalKey(jid types.WhatsAppID) database.PortalKey {
  625. return database.NewPortalKey(jid, user.JID)
  626. }
  627. func (user *User) GetPortalByJID(jid types.WhatsAppID) *Portal {
  628. return user.bridge.GetPortalByJID(user.PortalKey(jid))
  629. }
  630. func (user *User) handleMessageLoop() {
  631. for {
  632. select {
  633. case msg := <-user.messages:
  634. user.GetPortalByJID(msg.chat).messages <- msg
  635. case <-user.syncStart:
  636. user.syncWait.Wait()
  637. }
  638. }
  639. }
  640. func (user *User) putMessage(message PortalMessage) {
  641. select {
  642. case user.messages <- message:
  643. default:
  644. user.log.Warnln("Buffer is full, dropping message in", message.chat)
  645. }
  646. }
  647. func (user *User) HandleNewContact(contact whatsapp.Contact) {
  648. user.log.Debugfln("Contact message: %+v", contact)
  649. go func() {
  650. if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) {
  651. contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
  652. }
  653. puppet := user.bridge.GetPuppetByJID(contact.Jid)
  654. puppet.UpdateName(user, contact)
  655. }()
  656. }
  657. func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
  658. user.log.Debugfln("Battery message: %+v", battery)
  659. var notice string
  660. if !battery.Plugged && battery.Percentage < 15 && user.batteryWarningsSent < 1 {
  661. notice = fmt.Sprintf("Phone battery low (%d %% remaining)", battery.Percentage)
  662. user.batteryWarningsSent = 1
  663. } else if !battery.Plugged && battery.Percentage < 5 && user.batteryWarningsSent < 2 {
  664. notice = fmt.Sprintf("Phone battery very low (%d %% remaining)", battery.Percentage)
  665. user.batteryWarningsSent = 2
  666. } else if battery.Percentage > 15 || battery.Plugged {
  667. user.batteryWarningsSent = 0
  668. }
  669. if notice != "" {
  670. go user.sendBridgeNotice("%s", notice)
  671. }
  672. }
  673. func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
  674. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  675. }
  676. func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
  677. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  678. }
  679. func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
  680. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  681. }
  682. func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
  683. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  684. }
  685. func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
  686. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  687. }
  688. func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
  689. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  690. }
  691. func (user *User) HandleContactMessage(message whatsapp.ContactMessage) {
  692. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  693. }
  694. func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) {
  695. user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
  696. }
  697. func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) {
  698. user.putMessage(PortalMessage{message.RemoteJid, user, message, 0})
  699. }
  700. type FakeMessage struct {
  701. Text string
  702. ID string
  703. Alert bool
  704. }
  705. func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
  706. if info.Data != nil {
  707. return
  708. }
  709. data := FakeMessage{
  710. ID: info.ID,
  711. }
  712. switch info.Type {
  713. case whatsappExt.CallOffer:
  714. if !user.bridge.Config.Bridge.CallNotices.Start {
  715. return
  716. }
  717. data.Text = "Incoming call"
  718. data.Alert = true
  719. case whatsappExt.CallOfferVideo:
  720. if !user.bridge.Config.Bridge.CallNotices.Start {
  721. return
  722. }
  723. data.Text = "Incoming video call"
  724. data.Alert = true
  725. case whatsappExt.CallTerminate:
  726. if !user.bridge.Config.Bridge.CallNotices.End {
  727. return
  728. }
  729. data.Text = "Call ended"
  730. data.ID += "E"
  731. default:
  732. return
  733. }
  734. portal := user.GetPortalByJID(info.From)
  735. if portal != nil {
  736. portal.messages <- PortalMessage{info.From, user, data, 0}
  737. }
  738. }
  739. func (user *User) HandlePresence(info whatsappExt.Presence) {
  740. puppet := user.bridge.GetPuppetByJID(info.SenderJID)
  741. switch info.Status {
  742. case whatsapp.PresenceUnavailable:
  743. _ = puppet.DefaultIntent().SetPresence("offline")
  744. case whatsapp.PresenceAvailable:
  745. if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
  746. portal := user.bridge.GetPortalByMXID(puppet.typingIn)
  747. _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
  748. puppet.typingIn = ""
  749. puppet.typingAt = 0
  750. } else {
  751. _ = puppet.DefaultIntent().SetPresence("online")
  752. }
  753. case whatsapp.PresenceComposing:
  754. portal := user.GetPortalByJID(info.JID)
  755. if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
  756. if puppet.typingIn == portal.MXID {
  757. return
  758. }
  759. _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
  760. }
  761. puppet.typingIn = portal.MXID
  762. puppet.typingAt = time.Now().Unix()
  763. _, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000)
  764. }
  765. }
  766. func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
  767. if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead {
  768. portal := user.GetPortalByJID(info.ToJID)
  769. if len(portal.MXID) == 0 {
  770. return
  771. }
  772. go func() {
  773. intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
  774. for _, msgID := range info.IDs {
  775. msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID)
  776. if msg == nil {
  777. continue
  778. }
  779. err := intent.MarkRead(portal.MXID, msg.MXID)
  780. if err != nil {
  781. user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
  782. }
  783. }
  784. }()
  785. }
  786. }
  787. func (user *User) HandleReadMessage(read whatsapp.ReadMessage) {
  788. if strings.HasSuffix(read.Jid, whatsappExt.OldUserSuffix) {
  789. read.Jid = strings.Replace(read.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
  790. }
  791. puppet := user.bridge.GetPuppetByJID(user.JID)
  792. if puppet == nil {
  793. return
  794. }
  795. intent := puppet.CustomIntent()
  796. if intent == nil {
  797. return
  798. }
  799. portal := user.GetPortalByJID(read.Jid)
  800. if portal == nil {
  801. return
  802. }
  803. message := user.bridge.DB.Message.GetLastInChat(portal.Key)
  804. if message == nil {
  805. return
  806. }
  807. user.log.Debugfln("User read %s/%s in %s/%s in WhatsApp mobile", message.JID, message.MXID, portal.Key.JID, portal.MXID)
  808. err := intent.MarkRead(portal.MXID, message.MXID)
  809. if err != nil {
  810. user.log.Warnfln("Failed to bridge own read receipt in %s: %v", read.Jid, err)
  811. }
  812. }
  813. func (user *User) HandleCommand(cmd whatsappExt.Command) {
  814. switch cmd.Type {
  815. case whatsappExt.CommandPicture:
  816. if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) {
  817. puppet := user.bridge.GetPuppetByJID(cmd.JID)
  818. go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
  819. } else {
  820. portal := user.GetPortalByJID(cmd.JID)
  821. go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
  822. }
  823. case whatsappExt.CommandDisconnect:
  824. user.cleanDisconnection = true
  825. if cmd.Kind == "replaced" {
  826. go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" +
  827. "Use the `reconnect` command to disconnect the other client and resume bridging.")
  828. } else {
  829. user.log.Warnln("Unknown kind of disconnect:", string(cmd.Raw))
  830. go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server (reason code: %s).\n\n"+
  831. "Use the `reconnect` command to reconnect.", cmd.Kind)
  832. }
  833. }
  834. }
  835. func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
  836. if cmd.Command != whatsappExt.ChatUpdateCommandAction {
  837. return
  838. }
  839. portal := user.GetPortalByJID(cmd.JID)
  840. if len(portal.MXID) == 0 {
  841. if cmd.Data.Action == whatsappExt.ChatActionIntroduce || cmd.Data.Action == whatsappExt.ChatActionCreate {
  842. go func() {
  843. err := portal.CreateMatrixRoom(user)
  844. if err != nil {
  845. user.log.Errorln("Failed to create portal room after receiving join event:", err)
  846. }
  847. }()
  848. }
  849. return
  850. }
  851. switch cmd.Data.Action {
  852. case whatsappExt.ChatActionNameChange:
  853. go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, true)
  854. case whatsappExt.ChatActionAddTopic:
  855. go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, true)
  856. case whatsappExt.ChatActionRemoveTopic:
  857. go portal.UpdateTopic("", cmd.Data.SenderJID, true)
  858. case whatsappExt.ChatActionPromote:
  859. go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
  860. case whatsappExt.ChatActionDemote:
  861. go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
  862. case whatsappExt.ChatActionAnnounce:
  863. go portal.RestrictMessageSending(cmd.Data.Announce)
  864. case whatsappExt.ChatActionRestrict:
  865. go portal.RestrictMetadataChanges(cmd.Data.Restrict)
  866. case whatsappExt.ChatActionRemove:
  867. go portal.HandleWhatsAppKick(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
  868. case whatsappExt.ChatActionAdd:
  869. go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
  870. case whatsappExt.ChatActionIntroduce:
  871. if cmd.Data.SenderJID != "unknown" {
  872. go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID})
  873. }
  874. }
  875. }
  876. func (user *User) HandleJsonMessage(message string) {
  877. var msg json.RawMessage
  878. err := json.Unmarshal([]byte(message), &msg)
  879. if err != nil {
  880. return
  881. }
  882. user.log.Debugln("JSON message:", message)
  883. user.updateLastConnectionIfNecessary()
  884. }
  885. func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
  886. user.updateLastConnectionIfNecessary()
  887. }
  888. func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) {
  889. user.log.Debugfln("Unknown binary message: %+v", node)
  890. }
  891. func (user *User) NeedsRelaybot(portal *Portal) bool {
  892. return !user.HasSession() || !user.IsInPortal(portal.Key)
  893. }