diff --git a/commands.go b/commands.go index 537f98b..7360006 100644 --- a/commands.go +++ b/commands.go @@ -103,7 +103,7 @@ func (handler *CommandHandler) Handle(roomID types.MatrixRoomID, user *User, mes case "dev-test": handler.CommandDevTest(ce) case "login-matrix", "logout", "sync", "list", "open", "pm": - if ce.User.Conn == nil { + if !ce.User.HasSession() { ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.") return } else if !ce.User.IsConnected() { @@ -333,9 +333,9 @@ func (handler *CommandHandler) CommandSync(ce *CommandEvent) { } ce.Reply("Syncing contacts...") - user.syncPuppets() + user.syncPuppets(nil) ce.Reply("Syncing chats...") - user.syncPortals(create) + user.syncPortals(nil, create) ce.Reply("Sync complete.") } diff --git a/config/bridge.go b/config/bridge.go index e965bda..13e2c89 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -74,7 +74,7 @@ func (bc *BridgeConfig) setDefaults() { bc.MaxConnectionAttempts = 3 bc.ConnectionRetryDelay = -1 bc.ReportConnectionRetry = true - bc.ContactWaitDelay = 1 + bc.ContactWaitDelay = 30 bc.CallNotices.Start = true bc.CallNotices.End = true diff --git a/example-config.yaml b/example-config.yaml index b6ef72c..5d40866 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -75,9 +75,9 @@ bridge: # Whether or not the bridge should send a notice to the user's management room when it retries connecting. # If false, it will only report when it stops retrying. report_connection_retry: true - # Number of seconds to wait for contacts and chats to be sent at startup before syncing. - # If you have lots of chats, it might take more than a second. - contact_wait_delay: 1 + # Maximum number of seconds to wait for chats to be sent at startup. + # If this is too low and you have lots of chats, it could cause backfilling to fail. + contact_wait_delay: 30 # Whether or not to send call start/end notices to Matrix. call_notices: diff --git a/go.mod b/go.mod index 61236f1..8354bf5 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,6 @@ require ( ) replace ( - github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20190824193805-f9a7dc8e6a5f + github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.0.2-0.20190824212315-7c97464c8c54 gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 ) diff --git a/go.sum b/go.sum index 9c77549..039fd90 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/tulir/go-whatsapp v0.0.2-0.20190717210948-309b1b2cfa74 h1:Aqnz+nkJ/tB github.com/tulir/go-whatsapp v0.0.2-0.20190717210948-309b1b2cfa74/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8= github.com/tulir/go-whatsapp v0.0.2-0.20190824193805-f9a7dc8e6a5f h1:a6GstbsMbzd3Rteo64+30/US2U6iQF6e5mvXlOVRU3I= github.com/tulir/go-whatsapp v0.0.2-0.20190824193805-f9a7dc8e6a5f/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8= +github.com/tulir/go-whatsapp v0.0.2-0.20190824212315-7c97464c8c54 h1:Pe+DJtXEbTe+FhmOxrY74EbqfqGjZTxxaU3iLMqo0CA= +github.com/tulir/go-whatsapp v0.0.2-0.20190824212315-7c97464c8c54/go.mod h1:u3Hdptbz3iB5y/NEoSKgsp9hBzUlm0A5OrLMVdENAX8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/user.go b/user.go index 42f4f62..9500e06 100644 --- a/user.go +++ b/user.go @@ -55,6 +55,8 @@ type User struct { cleanDisconnection bool + syncPortalsDone chan struct{} + messages chan PortalMessage syncLock sync.Mutex } @@ -141,6 +143,7 @@ func (bridge *Bridge) NewUser(dbUser *database.User) *User { bridge: bridge, log: bridge.Log.Sub("User").Sub(string(dbUser.MXID)), + syncPortalsDone: make(chan struct{}, 1), messages: make(chan PortalMessage, 256), } user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID) @@ -346,25 +349,37 @@ func (user *User) PostLogin() { } func (user *User) intPostLogin() { - dur := time.Duration(user.bridge.Config.Bridge.ContactWaitDelay) * time.Second - user.log.Debugfln("Waiting %s for contacts to arrive", dur) - // Hacky way to wait for chats and contacts to arrive automatically - time.Sleep(dur) - user.log.Debugfln("Waited %s, have %d chats and %d contacts", dur, len(user.Conn.Store.Chats), len(user.Conn.Store.Contacts)) - user.createCommunity() - go user.syncPuppets() - user.syncPortals(false) - user.log.Debugln("Post-login sync complete, unlocking processing of incoming messages") + + select { + case <- user.syncPortalsDone: + user.log.Debugln("Post-login portal sync complete, unlocking processing of incoming messages.") + case <- time.After(time.Duration(user.bridge.Config.Bridge.ContactWaitDelay) * time.Second): + user.log.Warnln("Timed out waiting for chat list to arrive! Unlocking processing of incoming messages.") + } user.syncLock.Unlock() } -func (user *User) syncPortals(createAll bool) { - user.log.Infoln("Reading chat list") - chats := make(ChatList, 0, len(user.Conn.Store.Chats)) - existingKeys := user.GetInCommunityMap() - portalKeys := make([]database.PortalKeyWithMeta, 0, len(user.Conn.Store.Chats)) +func (user *User) HandleChatList(chats []whatsapp.Chat) { + chatMap := make(map[string]whatsapp.Chat) for _, chat := range user.Conn.Store.Chats { + chatMap[chat.Jid] = chat + } + for _, chat := range chats { + chatMap[chat.Jid] = chat + } + go user.syncPortals(chatMap, false) +} + +func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool) { + if chatMap == nil { + chatMap = user.Conn.Store.Chats + } + user.log.Infoln("Reading chat list") + chats := make(ChatList, 0, len(chatMap)) + existingKeys := user.GetInCommunityMap() + portalKeys := make([]database.PortalKeyWithMeta, 0, len(chatMap)) + for _, chat := range chatMap { ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64) if err != nil { user.log.Warnfln("Non-integer last message time in %s: %s", chat.Jid, chat.LastMessageTime) @@ -409,11 +424,26 @@ func (user *User) syncPortals(createAll bool) { } } user.log.Infoln("Finished syncing portals") + select { + case user.syncPortalsDone <- struct{}{}: + default: + } } -func (user *User) syncPuppets() { +func (user *User) HandleContactList(contacts []whatsapp.Contact) { + contactMap := make(map[string]whatsapp.Contact) + for _, contact := range contacts { + contactMap[contact.Jid] = contact + } + go user.syncPuppets(contactMap) +} + +func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) { + if contacts == nil { + contacts = user.Conn.Store.Contacts + } user.log.Infoln("Syncing puppet info from contacts") - for jid, contact := range user.Conn.Store.Contacts { + for jid, contact := range contacts { if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { puppet := user.bridge.GetPuppetByJID(contact.Jid) puppet.Sync(user, contact)