Browse Source

Update go-whatsapp to break everything and maybe improve things

Tulir Asokan 4 years ago
parent
commit
7bd47fabb2
10 changed files with 173 additions and 153 deletions
  1. 20 29
      commands.go
  2. 3 3
      config/bridge.go
  3. 3 3
      database/user.go
  4. 2 2
      go.mod
  5. 6 0
      go.sum
  6. 1 3
      main.go
  7. 2 2
      portal.go
  8. 19 33
      provisioning.go
  9. 2 2
      puppet.go
  10. 115 76
      user.go

+ 20 - 29
commands.go

@@ -17,6 +17,7 @@
 package main
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"math"
@@ -254,7 +255,7 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
 	handler.log.Debugln("%s successfully joined group %s", ce.User.MXID, jid)
 	portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(jid))
 	if len(portal.MXID) > 0 {
-		portal.Sync(ce.User, whatsapp.Contact{Jid: portal.Key.JID})
+		portal.Sync(ce.User, whatsapp.Contact{JID: portal.Key.JID})
 		ce.Reply("Successfully joined group \"%s\" and synced portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID)
 	} else {
 		err = portal.CreateMatrixRoom(ce.User)
@@ -411,11 +412,11 @@ func (handler *CommandHandler) CommandLogout(ce *CommandEvent) {
 		ce.Reply("Unknown error while logging out: %v", err)
 		return
 	}
-	ce.User.Disconnect()
 	ce.User.removeFromJIDMap()
 	// TODO this causes a foreign key violation, which should be fixed
 	//ce.User.JID = ""
 	ce.User.SetSession(nil)
+	ce.User.DeleteConnection()
 	ce.Reply("Logged out successfully.")
 }
 
@@ -469,9 +470,10 @@ func (handler *CommandHandler) CommandDeleteSession(ce *CommandEvent) {
 		ce.Reply("Nothing to purge: no session information stored and no active connection.")
 		return
 	}
-	ce.User.Disconnect()
+	//ce.User.JID = ""
 	ce.User.removeFromJIDMap()
 	ce.User.SetSession(nil)
+	ce.User.DeleteConnection()
 	ce.Reply("Session information purged")
 }
 
@@ -489,24 +491,21 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
 	}
 
 	wasConnected := true
-	sess, err := ce.User.Conn.Disconnect()
+	err := ce.User.Conn.Disconnect()
 	if err == whatsapp.ErrNotConnected {
 		wasConnected = false
 	} else if err != nil {
 		ce.User.log.Warnln("Error while disconnecting:", err)
-	} else {
-		ce.User.SetSession(&sess)
 	}
 
-	err = ce.User.Conn.Restore(true)
+	ctx := context.Background()
+
+	err = ce.User.Conn.Restore(true, ctx)
 	if err == whatsapp.ErrInvalidSession {
 		if ce.User.Session != nil {
 			ce.User.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
-			var sess whatsapp.Session
-			sess, err = ce.User.Conn.RestoreWithSession(*ce.User.Session)
-			if err == nil {
-				ce.User.SetSession(&sess)
-			}
+			ce.User.Conn.SetSession(*ce.User.Session)
+			err = ce.User.Conn.Restore(true, ctx)
 		} else {
 			ce.Reply("You are not logged in.")
 			return
@@ -520,17 +519,11 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
 	}
 	if err != nil {
 		ce.User.log.Warnln("Error while reconnecting:", err)
-		if errors.Is(err, whatsapp.ErrRestoreSessionTimeout) {
-			ce.Reply("Reconnection timed out. Is WhatsApp on your phone reachable?")
-		} else {
-			ce.Reply("Unknown error while reconnecting: %v", err)
-		}
+		ce.Reply("Unknown error while reconnecting: %v", err)
 		ce.User.log.Debugln("Disconnecting due to failed session restore in reconnect command...")
-		sess, err = ce.User.Conn.Disconnect()
+		err = ce.User.Conn.Disconnect()
 		if err != nil {
 			ce.User.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
-		} else {
-			ce.User.SetSession(&sess)
 		}
 		return
 	}
@@ -553,7 +546,7 @@ func (handler *CommandHandler) CommandDeleteConnection(ce *CommandEvent) {
 		ce.Reply("You don't have a WhatsApp connection.")
 		return
 	}
-	ce.User.Disconnect()
+	ce.User.DeleteConnection()
 	ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.")
 }
 
@@ -564,7 +557,7 @@ func (handler *CommandHandler) CommandDisconnect(ce *CommandEvent) {
 		ce.Reply("You don't have a WhatsApp connection.")
 		return
 	}
-	sess, err := ce.User.Conn.Disconnect()
+	err := ce.User.Conn.Disconnect()
 	if err == whatsapp.ErrNotConnected {
 		ce.Reply("You were not connected.")
 		return
@@ -572,8 +565,6 @@ func (handler *CommandHandler) CommandDisconnect(ce *CommandEvent) {
 		ce.User.log.Warnln("Error while disconnecting:", err)
 		ce.Reply("Unknown error while disconnecting: %v", err)
 		return
-	} else {
-		ce.User.SetSession(&sess)
 	}
 	ce.User.bridge.Metrics.TrackConnectionState(ce.User.JID, false)
 	ce.Reply("Successfully disconnected. Use the `reconnect` command to reconnect.")
@@ -741,9 +732,9 @@ func formatContacts(contacts bool, input map[string]whatsapp.Contact) (result []
 		}
 
 		if contacts {
-			result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.Jid[:len(contact.Jid)-len(whatsapp.NewUserSuffix)]))
+			result = append(result, fmt.Sprintf("* %s / %s - `%s`", contact.Name, contact.Notify, contact.JID[:len(contact.JID)-len(whatsapp.NewUserSuffix)]))
 		} else {
-			result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.Jid))
+			result = append(result, fmt.Sprintf("* %s - `%s`", contact.Name, contact.JID))
 		}
 	}
 	sort.Sort(sort.StringSlice(result))
@@ -875,11 +866,11 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
 				"To create a portal anyway, use `pm --force <number>`.")
 			return
 		}
-		contact = whatsapp.Contact{Jid: jid}
+		contact = whatsapp.Contact{JID: jid}
 	}
-	puppet := user.bridge.GetPuppetByJID(contact.Jid)
+	puppet := user.bridge.GetPuppetByJID(contact.JID)
 	puppet.Sync(user, contact)
-	portal := user.bridge.GetPortalByJID(database.NewPortalKey(contact.Jid, user.JID))
+	portal := user.bridge.GetPortalByJID(database.NewPortalKey(contact.JID, user.JID))
 	if len(portal.MXID) > 0 {
 		err := portal.MainIntent().EnsureInvited(portal.MXID, user.MXID)
 		if err != nil {

+ 3 - 3
config/bridge.go

@@ -165,8 +165,8 @@ type UsernameTemplateArgs struct {
 
 func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8) {
 	var buf bytes.Buffer
-	if index := strings.IndexRune(contact.Jid, '@'); index > 0 {
-		contact.Jid = "+" + contact.Jid[:index]
+	if index := strings.IndexRune(contact.JID, '@'); index > 0 {
+		contact.JID = "+" + contact.JID[:index]
 	}
 	bc.displaynameTemplate.Execute(&buf, contact)
 	var quality int8
@@ -175,7 +175,7 @@ func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8
 		quality = 3
 	case len(contact.Name) > 0 || len(contact.Short) > 0:
 		quality = 2
-	case len(contact.Jid) > 0:
+	case len(contact.JID) > 0:
 		quality = 1
 	default:
 		quality = 0

+ 3 - 3
database/user.go

@@ -93,7 +93,7 @@ func (user *User) Scan(row Scannable) *User {
 	if len(jid.String) > 0 && len(clientID.String) > 0 {
 		user.JID = jid.String + whatsapp.NewUserSuffix
 		user.Session = &whatsapp.Session{
-			ClientId:    clientID.String,
+			ClientID:    clientID.String,
 			ClientToken: clientToken.String,
 			ServerToken: serverToken.String,
 			EncKey:      encKey,
@@ -139,7 +139,7 @@ func (user *User) Insert() {
 	_, err := user.db.Exec(`INSERT INTO "user" (mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
 		user.MXID, user.jidPtr(),
 		user.ManagementRoom, user.LastConnection,
-		sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey)
+		sess.ClientID, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey)
 	if err != nil {
 		user.log.Warnfln("Failed to insert %s: %v", user.MXID, err)
 	}
@@ -158,7 +158,7 @@ func (user *User) Update() {
 	sess := user.sessionUnptr()
 	_, err := user.db.Exec(`UPDATE "user" SET jid=$1, management_room=$2, last_connection=$3, client_id=$4, client_token=$5, server_token=$6, enc_key=$7, mac_key=$8 WHERE mxid=$9`,
 		user.jidPtr(), user.ManagementRoom, user.LastConnection,
-		sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey,
+		sess.ClientID, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey,
 		user.MXID)
 	if err != nil {
 		user.log.Warnfln("Failed to update %s: %v", user.MXID, err)

+ 2 - 2
go.mod

@@ -12,8 +12,8 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	gopkg.in/yaml.v2 v2.3.0
 	maunium.net/go/mauflag v1.0.0
-	maunium.net/go/maulogger/v2 v2.1.1
+	maunium.net/go/maulogger/v2 v2.2.2
 	maunium.net/go/mautrix v0.8.2
 )
 
-replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.21
+replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.22-0.20210218211744-b9f35ff6257a

+ 6 - 0
go.sum

@@ -145,6 +145,8 @@ github.com/tulir/go-whatsapp v0.3.20 h1:nK92MgruqXwk+QlaAS39xhzHNbFvJIEgUIOUrN3i
 github.com/tulir/go-whatsapp v0.3.20/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
 github.com/tulir/go-whatsapp v0.3.21 h1:2m7gUw4oHX4kIpMmP9VwCR7KEUK/PHhXLygPFGF9XfI=
 github.com/tulir/go-whatsapp v0.3.21/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
+github.com/tulir/go-whatsapp v0.3.22-0.20210218211744-b9f35ff6257a h1:8JSW6oIAgI1TtR7wkvhNpTYhjKWBxk/eFyB8qXeOfyg=
+github.com/tulir/go-whatsapp v0.3.22-0.20210218211744-b9f35ff6257a/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
 golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -232,6 +234,10 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
 maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
 maunium.net/go/maulogger/v2 v2.1.1 h1:NAZNc6XUFJzgzfewCzVoGkxNAsblLCSSEdtDuIjP0XA=
 maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
+maunium.net/go/maulogger/v2 v2.2.1 h1:qwEDOdT7OhwqvFBXhSD0lqW2O2Oc/DbP/uv3zaai0W8=
+maunium.net/go/maulogger/v2 v2.2.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
+maunium.net/go/maulogger/v2 v2.2.2 h1:NCw+7Be1GQFm8xXJ4M2C0Q8yLBTx3c5s7UZ4y1anZMU=
+maunium.net/go/maulogger/v2 v2.2.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
 maunium.net/go/mautrix v0.8.0-rc.3 h1:bb18oNxHUmeiJ0V63YTRVGMjgoeLwu+G40l4n42Z5GI=
 maunium.net/go/mautrix v0.8.0-rc.3/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
 maunium.net/go/mautrix v0.8.0-rc.4 h1:3JXoL2JJPE5nh/YSw9sv9dQA9ulma9yHTMOBMBY1xdo=

+ 1 - 3
main.go

@@ -384,11 +384,9 @@ func (bridge *Bridge) Stop() {
 			continue
 		}
 		bridge.Log.Debugln("Disconnecting", user.MXID)
-		sess, err := user.Conn.Disconnect()
+		err := user.Conn.Disconnect()
 		if err != nil {
 			bridge.Log.Errorfln("Error while disconnecting %s: %v", user.MXID, err)
-		} else {
-			user.SetSession(&sess)
 		}
 	}
 }

+ 2 - 2
portal.go

@@ -1771,7 +1771,7 @@ func (portal *Portal) convertGifToVideo(gif []byte) ([]byte, error) {
 		"-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart",
 		"-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'",
 		outputFileName)
-	vcLog := portal.log.Sub("VideoConverter").WithDefaultLevel(log.LevelWarn)
+	vcLog := portal.log.Sub("VideoConverter").Writer(log.LevelWarn)
 	cmd.Stdout = vcLog
 	cmd.Stderr = vcLog
 
@@ -2319,7 +2319,7 @@ func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
 			return
 		}
 		portal.Topic = content.Topic
-		resp, err = sender.Conn.UpdateGroupDescription(portal.Key.JID, content.Topic)
+		resp, err = sender.Conn.UpdateGroupDescription(sender.JID, portal.Key.JID, content.Topic)
 	case *event.RoomAvatarEventContent:
 		return
 	}

+ 19 - 33
provisioning.go

@@ -125,7 +125,7 @@ func (prov *ProvisioningAPI) DeleteSession(w http.ResponseWriter, r *http.Reques
 		})
 		return
 	}
-	user.Disconnect()
+	user.DeleteConnection()
 	user.SetSession(nil)
 	jsonResponse(w, http.StatusOK, Response{true, "Session information purged"})
 }
@@ -139,7 +139,7 @@ func (prov *ProvisioningAPI) DeleteConnection(w http.ResponseWriter, r *http.Req
 		})
 		return
 	}
-	user.Disconnect()
+	user.DeleteConnection()
 	jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp and connection deleted"})
 }
 
@@ -152,7 +152,7 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request)
 		})
 		return
 	}
-	sess, err := user.Conn.Disconnect()
+	err := user.Conn.Disconnect()
 	if err == whatsapp.ErrNotConnected {
 		jsonResponse(w, http.StatusNotFound, Error{
 			Error:   "You were not connected",
@@ -166,8 +166,6 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request)
 			ErrCode: err.Error(),
 		})
 		return
-	} else {
-		user.SetSession(&sess)
 	}
 	user.bridge.Metrics.TrackConnectionState(user.JID, false)
 	jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp"})
@@ -190,25 +188,21 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
 
 	user.log.Debugln("Received /reconnect request, disconnecting")
 	wasConnected := true
-	sess, err := user.Conn.Disconnect()
+	err := user.Conn.Disconnect()
 	if err == whatsapp.ErrNotConnected {
 		wasConnected = false
 	} else if err != nil {
 		user.log.Warnln("Error while disconnecting:", err)
-	} else {
-		user.SetSession(&sess)
 	}
 
 	user.log.Debugln("Restoring session for /reconnect")
-	err = user.Conn.Restore(true)
+	err = user.Conn.Restore(true, r.Context())
 	user.log.Debugfln("Restore session for /reconnect responded with %v", err)
 	if err == whatsapp.ErrInvalidSession {
 		if user.Session != nil {
 			user.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...")
-			sess, err = user.Conn.RestoreWithSession(*user.Session)
-			if err == nil {
-				user.SetSession(&sess)
-			}
+			user.Conn.SetSession(*user.Session)
+			err = user.Conn.Restore(true, r.Context())
 		} else {
 			jsonResponse(w, http.StatusForbidden, Error{
 				Error:   "You're not logged in",
@@ -216,7 +210,8 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
 			})
 			return
 		}
-	} else if err == whatsapp.ErrLoginInProgress {
+	}
+	if err == whatsapp.ErrLoginInProgress {
 		jsonResponse(w, http.StatusConflict, Error{
 			Error:   "A login or reconnection is already in progress.",
 			ErrCode: "login in progress",
@@ -231,23 +226,14 @@ func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) {
 	}
 	if err != nil {
 		user.log.Warnln("Error while reconnecting:", err)
-		if errors.Is(err, whatsapp.ErrRestoreSessionTimeout) {
-			jsonResponse(w, http.StatusForbidden, Error{
-				Error:   "Reconnection timed out. Is WhatsApp on your phone reachable?",
-				ErrCode: err.Error(),
-			})
-		} else {
-			jsonResponse(w, http.StatusForbidden, Error{
-				Error:   fmt.Sprintf("Unknown error while reconnecting: %v", err),
-				ErrCode: err.Error(),
-			})
-		}
+		jsonResponse(w, http.StatusInternalServerError, Error{
+			Error:   fmt.Sprintf("Unknown error while reconnecting: %v", err),
+			ErrCode: err.Error(),
+		})
 		user.log.Debugln("Disconnecting due to failed session restore in reconnect command...")
-		sess, err := user.Conn.Disconnect()
+		err = user.Conn.Disconnect()
 		if err != nil {
 			user.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err)
-		} else {
-			user.SetSession(&sess)
 		}
 		return
 	}
@@ -339,7 +325,7 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
 				return
 			}
 		}
-		user.Disconnect()
+		user.DeleteConnection()
 	}
 
 	user.bridge.Metrics.TrackConnectionState(user.JID, false)
@@ -407,7 +393,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
 	})
 
 	user.log.Debugln("Starting login via provisioning API")
-	session, err := user.Conn.LoginWithRetry(qrChan, ctx, user.bridge.Config.Bridge.LoginQRRegenCount)
+	session, jid, err := user.Conn.Login(qrChan, ctx, user.bridge.Config.Bridge.LoginQRRegenCount)
 	qrChan <- "stop"
 	if err != nil {
 		var msg string
@@ -419,7 +405,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
 			msg = "QR code scan timed out. Please try again."
 		} else if errors.Is(err, whatsapp.ErrInvalidWebsocket) {
 			msg = "WhatsApp connection error. Please try again."
-			user.Disconnect()
+			// TODO might need to make sure it reconnects?
 		} else {
 			msg = fmt.Sprintf("Unknown error while logging in: %v", err)
 		}
@@ -430,9 +416,9 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
 		})
 		return
 	}
-	user.log.Debugln("Successful login via provisioning API")
+	user.log.Debugln("Successful login as", jid, "via provisioning API")
 	user.ConnectionErrors = 0
-	user.JID = strings.Replace(user.Conn.Info.Wid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
+	user.JID = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
 	user.addToJIDMap()
 	user.SetSession(&session)
 	_ = c.WriteJSON(map[string]interface{}{

+ 2 - 2
puppet.go

@@ -291,8 +291,8 @@ func (puppet *Puppet) Sync(source *User, contact whatsapp.Contact) {
 		puppet.log.Errorln("Failed to ensure registered:", err)
 	}
 
-	if contact.Jid == source.JID {
-		contact.Notify = source.Conn.Info.Pushname
+	if contact.JID == source.JID {
+		contact.Notify = source.pushName
 	}
 
 	update := false

+ 115 - 76
user.go

@@ -17,6 +17,7 @@
 package main
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -61,6 +62,7 @@ type User struct {
 	cleanDisconnection  bool
 	batteryWarningsSent int
 	lastReconnection    int64
+	pushName            string
 
 	chatListReceived chan struct{}
 	syncPortalsDone  chan struct{}
@@ -71,8 +73,9 @@ type User struct {
 	syncStart chan struct{}
 	syncWait  sync.WaitGroup
 
-	mgmtCreateLock sync.Mutex
-	connLock       sync.Mutex
+	mgmtCreateLock  sync.Mutex
+	connLock        sync.Mutex
+	cancelReconnect func()
 }
 
 func (bridge *Bridge) GetUserByMXID(userID id.UserID) *User {
@@ -234,55 +237,56 @@ func (user *User) SetSession(session *whatsapp.Session) {
 
 func (user *User) Connect(evenIfNoSession bool) bool {
 	user.connLock.Lock()
-	if user.Conn != nil && user.Conn.IsConnected() {
+	if user.Conn != nil {
 		user.connLock.Unlock()
-		return true
+		if user.Conn.IsConnected() {
+			return true
+		} else {
+			return user.RestoreSession()
+		}
 	} else if !evenIfNoSession && user.Session == nil {
 		user.connLock.Unlock()
 		return false
 	}
-	if user.Conn != nil {
-		user.Disconnect()
-	}
 	user.log.Debugln("Connecting to WhatsApp")
 	timeout := time.Duration(user.bridge.Config.Bridge.ConnectionTimeout)
 	if timeout == 0 {
 		timeout = 20
 	}
-	conn, err := whatsapp.NewConnWithOptions(&whatsapp.Options{
+	user.Conn = whatsapp.NewConn(&whatsapp.Options{
 		Timeout:         timeout * time.Second,
 		LongClientName:  user.bridge.Config.WhatsApp.OSName,
 		ShortClientName: user.bridge.Config.WhatsApp.BrowserName,
 		ClientVersion:   WAVersion,
+		Log:             user.log.Sub("Conn"),
+		Handler:         []whatsapp.Handler{user},
 	})
-	if err != nil {
-		user.log.Errorln("Failed to connect to WhatsApp:", err)
-		user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp server. " +
-			"This indicates a network problem on the bridge server. See bridge logs for more info.")
-		user.connLock.Unlock()
-		return false
-	}
-	user.Conn = conn
-	user.log.Debugln("WhatsApp connection successful")
-	user.Conn.AddHandler(user)
 	user.connLock.Unlock()
 	return user.RestoreSession()
 }
 
-func (user *User) Disconnect() {
-	sess, err := user.Conn.Disconnect()
+func (user *User) DeleteConnection() {
+	user.connLock.Lock()
+	if user.Conn == nil {
+		user.connLock.Unlock()
+		return
+	}
+	err := user.Conn.Disconnect()
 	if err != nil && err != whatsapp.ErrNotConnected {
 		user.log.Warnln("Error disconnecting: %v", err)
 	}
-	user.SetSession(&sess)
 	user.Conn.RemoveHandlers()
 	user.Conn = nil
 	user.bridge.Metrics.TrackConnectionState(user.JID, false)
+	user.connLock.Unlock()
 }
 
 func (user *User) RestoreSession() bool {
 	if user.Session != nil {
-		sess, err := user.Conn.RestoreWithSession(*user.Session)
+		user.Conn.SetSession(*user.Session)
+		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
+		defer cancel()
+		err := user.Conn.Restore(true, ctx)
 		if err == whatsapp.ErrAlreadyLoggedIn {
 			return true
 		} else if err != nil {
@@ -290,24 +294,23 @@ func (user *User) RestoreSession() bool {
 			if errors.Is(err, whatsapp.ErrUnpaired) {
 				user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " +
 					"To re-pair your phone, log in again.")
-				user.Disconnect()
 				user.removeFromJIDMap()
 				//user.JID = ""
 				user.SetSession(nil)
+				user.DeleteConnection()
 				return false
 			} else {
 				user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " +
 					"on your phone is reachable and use `reconnect` to try connecting again.")
 			}
 			user.log.Debugln("Disconnecting due to failed session restore...")
-			_, err := user.Conn.Disconnect()
+			err = user.Conn.Disconnect()
 			if err != nil {
 				user.log.Errorln("Failed to disconnect after failed session restore:", err)
 			}
 			return false
 		}
 		user.ConnectionErrors = 0
-		user.SetSession(&sess)
 		user.log.Debugln("Session restored successfully")
 		user.PostLogin()
 	}
@@ -382,7 +385,7 @@ func (user *User) Login(ce *CommandEvent) {
 	qrChan := make(chan string, 3)
 	eventIDChan := make(chan id.EventID, 1)
 	go user.loginQrChannel(ce, qrChan, eventIDChan)
-	session, err := user.Conn.LoginWithRetry(qrChan, nil, user.bridge.Config.Bridge.LoginQRRegenCount)
+	session, jid, err := user.Conn.Login(qrChan, nil, user.bridge.Config.Bridge.LoginQRRegenCount)
 	qrChan <- "stop"
 	if err != nil {
 		var eventID id.EventID
@@ -416,8 +419,9 @@ func (user *User) Login(ce *CommandEvent) {
 	}
 	// TODO there's a bit of duplication between this and the provisioning API login method
 	//      Also between the two logout methods (commands.go and provisioning.go)
+	user.log.Debugln("Successful login as", jid, "via command")
 	user.ConnectionErrors = 0
-	user.JID = strings.Replace(user.Conn.Info.Wid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
+	user.JID = strings.Replace(jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, 1)
 	user.addToJIDMap()
 	user.SetSession(&session)
 	ce.Reply("Successfully logged in, synchronizing chats...")
@@ -499,31 +503,31 @@ func (user *User) sendMarkdownBridgeAlert(formatString string, args ...interface
 	}
 }
 
-func (user *User) postConnPing(conn *whatsapp.Conn) bool {
-	if user.Conn != conn {
-		user.log.Warnln("Connection changed before scheduled post-connection ping, canceling ping")
-		return false
-	}
+func (user *User) postConnPing() bool {
 	user.log.Debugln("Making post-connection ping")
-	err := conn.AdminTest()
-	if err != nil {
-		user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err)
-		sess, disconnectErr := conn.Disconnect()
-		if disconnectErr != nil {
-			user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr)
+	var err error
+	for i := 0; ; i++ {
+		err = user.Conn.AdminTest()
+		if err == nil {
+			user.log.Debugln("Post-connection ping OK")
+			return true
+		} else if errors.Is(err, whatsapp.ErrConnectionTimeout) && i < 5 {
+			user.log.Warnfln("Post-connection ping timed out, sending new one")
 		} else {
-			user.Session = &sess
+			break
 		}
-		user.bridge.Metrics.TrackDisconnection(user.MXID)
-		go func() {
-			time.Sleep(1 * time.Second)
-			user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
-		}()
-		return false
-	} else {
-		user.log.Debugln("Post-connection ping OK")
-		return true
 	}
+	user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err)
+	disconnectErr := user.Conn.Disconnect()
+	if disconnectErr != nil {
+		user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr)
+	}
+	user.bridge.Metrics.TrackDisconnection(user.MXID)
+	go func() {
+		time.Sleep(1 * time.Second)
+		user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
+	}()
+	return false
 }
 
 func (user *User) intPostLogin(conn *whatsapp.Conn) {
@@ -538,11 +542,11 @@ func (user *User) intPostLogin(conn *whatsapp.Conn) {
 		user.log.Debugln("Chat list receive confirmation received in PostLogin")
 	case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second):
 		user.log.Warnln("Timed out waiting for chat list to arrive!")
-		user.postConnPing(conn)
+		user.postConnPing()
 		return
 	}
 
-	if !user.postConnPing(conn) {
+	if !user.postConnPing() {
 		user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.")
 		return
 	}
@@ -557,16 +561,14 @@ func (user *User) intPostLogin(conn *whatsapp.Conn) {
 	}
 }
 
-type InfoGetter interface {
+type NormalMessage interface {
 	GetInfo() whatsapp.MessageInfo
 }
 
 func (user *User) HandleEvent(event interface{}) {
 	switch v := event.(type) {
-	case whatsapp.TextMessage, whatsapp.ImageMessage, whatsapp.StickerMessage, whatsapp.VideoMessage,
-		whatsapp.AudioMessage, whatsapp.DocumentMessage, whatsapp.ContactMessage, whatsapp.StubMessage,
-		whatsapp.LocationMessage:
-		info := v.(InfoGetter).GetInfo()
+	case NormalMessage:
+		info := v.GetInfo()
 		user.messageInput <- PortalMessage{info.RemoteJid, user, v, info.Timestamp}
 	case whatsapp.MessageRevocation:
 		user.messageInput <- PortalMessage{v.RemoteJid, user, v, 0}
@@ -596,6 +598,8 @@ func (user *User) HandleEvent(event interface{}) {
 		user.HandleCommand(v)
 	case whatsapp.ChatUpdate:
 		user.HandleChatUpdate(v)
+	case whatsapp.ConnInfo:
+		user.HandleConnInfo(v)
 	case json.RawMessage:
 		user.HandleJSONMessage(v)
 	case *waProto.WebMessageInfo:
@@ -614,11 +618,11 @@ func (user *User) HandleStreamEvent(evt whatsapp.StreamEvent) {
 		if user.lastReconnection+60 > time.Now().Unix() {
 			user.lastReconnection = 0
 			user.log.Infoln("Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds")
-			conn := user.Conn
 			go func() {
 				time.Sleep(20 * time.Second)
 				// TODO if this happens during the post-login sync, it can get stuck forever
-				user.postConnPing(conn)
+				// TODO check if the above is still true
+				user.postConnPing()
 			}()
 		}
 	} else {
@@ -630,10 +634,10 @@ func (user *User) HandleChatList(chats []whatsapp.Chat) {
 	user.log.Infoln("Chat list received")
 	chatMap := make(map[string]whatsapp.Chat)
 	for _, chat := range user.Conn.Store.Chats {
-		chatMap[chat.Jid] = chat
+		chatMap[chat.JID] = chat
 	}
 	for _, chat := range chats {
-		chatMap[chat.Jid] = chat
+		chatMap[chat.JID] = chat
 	}
 	select {
 	case user.chatListReceived <- struct{}{}:
@@ -655,14 +659,14 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 	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)
+			user.log.Warnfln("Non-integer last message time in %s: %s", chat.JID, chat.LastMessageTime)
 			continue
 		}
-		portal := user.GetPortalByJID(chat.Jid)
+		portal := user.GetPortalByJID(chat.JID)
 
 		chats = append(chats, Chat{
 			Portal:          portal,
-			Contact:         user.Conn.Store.Contacts[chat.Jid],
+			Contact:         user.Conn.Store.Contacts[chat.JID],
 			LastMessageTime: ts,
 		})
 		var inCommunity, ok bool
@@ -777,7 +781,7 @@ func (user *User) UpdateDirectChats(chats map[id.UserID][]id.RoomID) {
 func (user *User) HandleContactList(contacts []whatsapp.Contact) {
 	contactMap := make(map[string]whatsapp.Contact)
 	for _, contact := range contacts {
-		contactMap[contact.Jid] = contact
+		contactMap[contact.JID] = contact
 	}
 	go user.syncPuppets(contactMap)
 }
@@ -786,10 +790,20 @@ func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) {
 	if contacts == nil {
 		contacts = user.Conn.Store.Contacts
 	}
+
+	_, hasSelf := contacts[user.JID]
+	if !hasSelf {
+		contacts[user.JID] = whatsapp.Contact{
+			Name:   user.pushName,
+			Notify: user.pushName,
+			JID:    user.JID,
+		}
+	}
+
 	user.log.Infoln("Syncing puppet info from contacts")
 	for jid, contact := range contacts {
 		if strings.HasSuffix(jid, whatsapp.NewUserSuffix) {
-			puppet := user.bridge.GetPuppetByJID(contact.Jid)
+			puppet := user.bridge.GetPuppetByJID(contact.JID)
 			puppet.Sync(user, contact)
 		}
 	}
@@ -846,14 +860,18 @@ func (user *User) tryReconnect(msg string) {
 		baseDelay = -baseDelay + 1
 	}
 	delay := baseDelay
-	conn := user.Conn
 	takeover := false
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	user.cancelReconnect = cancel
 	for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
-		if user.Conn != conn {
-			user.log.Debugln("Connection was recreated, aborting reconnection attempts")
+		select {
+		case <-ctx.Done():
+			user.log.Debugln("tryReconnect context cancelled, aborting reconnection attempts")
 			return
+		default:
 		}
-		err := conn.Restore(takeover)
+		err := user.Conn.Restore(takeover, ctx)
 		takeover = true
 		if err == nil {
 			user.ConnectionErrors = 0
@@ -863,14 +881,23 @@ func (user *User) tryReconnect(msg string) {
 			user.PostLogin()
 			return
 		} else if errors.Is(err, whatsapp.ErrBadRequest) {
-			user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...")
-			sess, err := conn.Disconnect()
+			user.log.Warnln("Got init 400 error when trying to reconnect, resetting connection...")
+			err = user.Conn.Disconnect()
 			if err != nil {
 				user.log.Debugln("Error while disconnecting for connection reset:", err)
 			}
-			user.SetSession(&sess)
+		} else if errors.Is(err, whatsapp.ErrUnpaired) {
+			user.log.Errorln("Got init 401 (unpaired) error when trying to reconnect, not retrying")
+			user.removeFromJIDMap()
+			//user.JID = ""
+			user.SetSession(nil)
+			user.DeleteConnection()
+			user.sendMarkdownBridgeAlert("\u26a0 Failed to reconnect to WhatsApp: unpaired from phone. " +
+				"To re-pair your phone, log in again.")
+			return
+		} else {
+			user.log.Errorln("Error while trying to reconnect after disconnection:", err)
 		}
-		user.log.Errorln("Error while trying to reconnect after disconnection:", err)
 		tries++
 		user.ConnectionErrors++
 		if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
@@ -930,10 +957,10 @@ func (user *User) handleMessageLoop() {
 
 func (user *User) HandleNewContact(contact whatsapp.Contact) {
 	user.log.Debugfln("Contact message: %+v", contact)
-	if strings.HasSuffix(contact.Jid, whatsapp.OldUserSuffix) {
-		contact.Jid = strings.Replace(contact.Jid, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
+	if strings.HasSuffix(contact.JID, whatsapp.OldUserSuffix) {
+		contact.JID = strings.Replace(contact.JID, whatsapp.OldUserSuffix, whatsapp.NewUserSuffix, -1)
 	}
-	puppet := user.bridge.GetPuppetByJID(contact.Jid)
+	puppet := user.bridge.GetPuppetByJID(contact.JID)
 	puppet.UpdateName(user, contact)
 }
 
@@ -1176,11 +1203,23 @@ func (user *User) HandleChatUpdate(cmd whatsapp.ChatUpdate) {
 		go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs)
 	case whatsapp.ChatActionIntroduce:
 		if cmd.Data.SenderJID != "unknown" {
-			go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID})
+			go portal.Sync(user, whatsapp.Contact{JID: portal.Key.JID})
 		}
 	}
 }
 
+func (user *User) HandleConnInfo(info whatsapp.ConnInfo) {
+	if user.Session != nil && info.Connected && len(info.ClientToken) > 0 {
+		user.log.Debugln("Received new tokens")
+		user.Session.ClientToken = info.ClientToken
+		user.Session.ServerToken = info.ServerToken
+		user.Session.Wid = info.WID
+	}
+	if len(info.PushName) > 0 {
+		user.pushName = info.PushName
+	}
+}
+
 func (user *User) HandleJSONMessage(message json.RawMessage) {
 	if !json.Valid(message) {
 		return