user.go 27 KB

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