Update go-whatsapp to break everything and maybe improve things

This commit is contained in:
Tulir Asokan 2021-02-18 23:36:14 +02:00
parent ca118e8678
commit 7bd47fabb2
10 changed files with 173 additions and 153 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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)

4
go.mod
View file

@ -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
go.sum
View file

@ -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=

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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{}{

View file

@ -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

191
user.go
View file

@ -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