diff --git a/CHANGELOG.md b/CHANGELOG.md index 560e2b5..5c0314e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,3 @@ -# v26.06 - -* Added placeholder for group message history share notices. -* Fixed community spaces not being bridged properly in some cases. -* Fixed handling edits of HD media. -* Fixed duplicate message when a message from another own device is initially - undecryptable and later gets resolved. - # v26.05 * Added support for importing sticker packs from WhatsApp. diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go new file mode 100644 index 0000000..f0527b2 --- /dev/null +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -0,0 +1,153 @@ +package main + +import ( + "net/http" + "strings" + + "github.com/rs/zerolog/hlog" + "go.mau.fi/util/exhttp" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/types" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/matrix" + "maunium.net/go/mautrix/id" + + "go.mau.fi/mautrix-whatsapp/pkg/connector" + "go.mau.fi/mautrix-whatsapp/pkg/waid" +) + +type OtherUserInfo struct { + MXID id.UserID `json:"mxid"` + JID types.JID `json:"jid"` + Name string `json:"displayname"` + Avatar id.ContentURIString `json:"avatar_url"` +} + +type PortalInfo struct { + RoomID id.RoomID `json:"room_id"` + OtherUser *OtherUserInfo `json:"other_user,omitempty"` + GroupInfo *types.GroupInfo `json:"group_info,omitempty"` + JustCreated bool `json:"just_created"` +} + +type Error struct { + Success bool `json:"success"` + Error string `json:"error"` + ErrCode string `json:"errcode"` +} + +func legacyProvContacts(w http.ResponseWriter, r *http.Request) { + userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) + if userLogin == nil { + return + } + if contacts, err := userLogin.Client.(*connector.WhatsAppClient).GetStore().Contacts.GetAllContacts(r.Context()); err != nil { + hlog.FromRequest(r).Err(err).Msg("Failed to fetch all contacts") + exhttp.WriteJSONResponse(w, http.StatusInternalServerError, Error{ + Error: "Internal server error while fetching contact list", + ErrCode: "failed to get contacts", + }) + } else { + augmentedContacts := map[types.JID]any{} + for jid, contact := range contacts { + var avatarURL id.ContentURIString + if puppet, _ := m.Bridge.GetExistingGhostByID(r.Context(), waid.MakeUserID(jid)); puppet != nil { + avatarURL = puppet.AvatarMXC + } + augmentedContacts[jid] = map[string]interface{}{ + "Found": contact.Found, + "FirstName": contact.FirstName, + "FullName": contact.FullName, + "PushName": contact.PushName, + "BusinessName": contact.BusinessName, + "AvatarURL": avatarURL, + } + } + exhttp.WriteJSONResponse(w, http.StatusOK, augmentedContacts) + } +} + +func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) { + number := r.PathValue("number") + userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) + if userLogin == nil { + return + } + startChat := strings.Contains(r.URL.Path, "/v1/pm/") + resp, err := userLogin.Client.(*connector.WhatsAppClient).ResolveIdentifier(r.Context(), number, startChat) + if err != nil { + hlog.FromRequest(r).Warn().Err(err).Str("identifier", number).Msg("Failed to resolve identifier") + matrix.RespondWithError(w, err, "Internal error resolving identifier") + return + } + var portal *bridgev2.Portal + if startChat { + portal, err = m.Bridge.GetPortalByKey(r.Context(), resp.Chat.PortalKey) + if err != nil { + hlog.FromRequest(r).Warn().Err(err).Stringer("portal_key", resp.Chat.PortalKey).Msg("Failed to get portal by key") + matrix.RespondWithError(w, err, "Internal error getting portal by key") + return + } + err = portal.CreateMatrixRoom(r.Context(), userLogin, nil) + if err != nil { + hlog.FromRequest(r).Warn().Err(err).Stringer("portal_key", resp.Chat.PortalKey).Msg("Failed to create matrix room for portal") + matrix.RespondWithError(w, err, "Internal error creating matrix room for portal") + return + } + } else { + portal, _ = m.Bridge.GetExistingPortalByKey(r.Context(), resp.Chat.PortalKey) + } + var roomID id.RoomID + if portal != nil { + roomID = portal.MXID + } + exhttp.WriteJSONResponse(w, http.StatusOK, PortalInfo{ + RoomID: roomID, + OtherUser: &OtherUserInfo{ + JID: waid.ParseUserID(resp.UserID), + MXID: resp.Ghost.Intent.GetMXID(), + Name: resp.Ghost.Name, + Avatar: resp.Ghost.AvatarMXC, + }, + }) +} + +func provAppStateDebug(w http.ResponseWriter, r *http.Request) { + userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) + if userLogin == nil { + return + } + client := userLogin.Client.(*connector.WhatsAppClient) + if client.Client == nil { + mautrix.MNotFound.WithMessage("WhatsApp client not connected").Write(w) + return + } + client.Client.AppStateDebugLogs = true + err := client.Client.FetchAppState(r.Context(), appstate.WAPatchName(r.PathValue("patch")), r.URL.Query().Get("full") == "1", false) + client.Client.AppStateDebugLogs = false + if err != nil { + mautrix.MUnknown.WithMessage("Failed to fetch app state: %v", err).Write(w) + } else { + exhttp.WriteEmptyJSONResponse(w, http.StatusOK) + } +} + +func provRecoverAppStateDebug(w http.ResponseWriter, r *http.Request) { + userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) + if userLogin == nil { + return + } + client := userLogin.Client.(*connector.WhatsAppClient) + if client.Client == nil { + mautrix.MNotFound.WithMessage("WhatsApp client not connected").Write(w) + return + } + resp, err := client.Client.SendPeerMessage(r.Context(), whatsmeow.BuildAppStateRecoveryRequest(appstate.WAPatchName(r.PathValue("patch")))) + if err != nil { + mautrix.MUnknown.WithMessage("Failed to send app state recovery request: %v", err).Write(w) + } else { + exhttp.WriteJSONResponse(w, http.StatusOK, resp) + } +} diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 07baaec..cb1632c 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -18,12 +18,21 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "26.06", + Version: "26.05", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } func main() { + m.PostStart = func() { + if m.Matrix.Provisioning != nil { + m.Matrix.Provisioning.Router.HandleFunc("GET /v1/contacts", legacyProvContacts) + m.Matrix.Provisioning.Router.HandleFunc("GET /v1/resolve_identifier/{number}", legacyProvResolveIdentifier) + m.Matrix.Provisioning.Router.HandleFunc("POST /v1/pm/{number}", legacyProvResolveIdentifier) + m.Matrix.Provisioning.Router.HandleFunc("POST /v1/debug/appstate/{patch}", provAppStateDebug) + m.Matrix.Provisioning.Router.HandleFunc("POST /v1/debug/recover-appstate/{patch}", provRecoverAppStateDebug) + } + } m.InitVersion(Tag, Commit, BuildTime) m.Run() } diff --git a/go.mod b/go.mod index 9dc2e58..c52b925 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.4 +toolchain go1.26.3 tool go.mau.fi/util/cmd/maubuild @@ -10,28 +10,28 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 - go.mau.fi/util v0.9.10 - go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b - golang.org/x/image v0.42.0 - golang.org/x/net v0.56.0 - golang.org/x/sync v0.21.0 + go.mau.fi/util v0.9.9 + go.mau.fi/webp v0.2.0 + go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 + golang.org/x/image v0.40.0 + golang.org/x/net v0.54.0 + golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 + maunium.net/go/mautrix v0.28.0 ) require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/beeper/argo-go v1.1.2 // indirect - github.com/coder/websocket v1.8.15 // indirect + github.com/coder/websocket v1.8.14 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.45 // indirect + github.com/mattn/go-sqlite3 v1.14.44 // indirect github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect @@ -41,13 +41,13 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect github.com/yuin/goldmark v1.8.2 // indirect - go.mau.fi/libsignal v0.2.2 // indirect + go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1d4b8af..5fe1561 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs= github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4= -github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= -github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -34,8 +34,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk= -github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -69,33 +69,33 @@ github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTd github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= -go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= -go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= -go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= -go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b h1:ZUk1ErarDNpnbosXR/MeOz2gkqA4S1bh8zjaSRj7N+Y= -go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= +go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= +go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= +go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= +go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= +go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= +go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 h1:rRcG2I1LVQ11kH2+KSYxNeFNZXp1oKA5Yxmagsf5bks= +go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= -golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY= -golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= +golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y= -maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.28.0 h1:vBakLzf8MAdfED3NzAKiMeKQbc3AQ4EAS03NC+TVMXQ= +maunium.net/go/mautrix v0.28.0/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index ee92ac2..b4ef181 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -260,9 +260,6 @@ func (wa *WhatsAppClient) handleWAHistorySync( Str("msg_id", rawMsg.GetMessage().GetKey().GetID()). Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()). Msg("Dropping historical message due to parse error") - log.Trace(). - Any("web_message_info", rawMsg.GetMessage()). - Msg("Content of event that failed to parse") continue } if firstItemTime.IsZero() { @@ -400,8 +397,8 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { return } wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true) - if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) { - log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID). + if errors.Is(err, whatsmeow.ErrNotInGroup) { + log.Debug().Stringer("chat_jid", conv.ChatJID). Msg("Skipping creating room because the user is not a participant") //err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID) //if err != nil { @@ -606,9 +603,6 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( Str("msg_id", msg.GetKey().GetID()). Uint64("msg_time_seconds", msg.GetMessageTimestamp()). Msg("Dropping historical message due to parse error") - zerolog.Ctx(ctx).Trace(). - Any("web_message_info", msg.GetMessage()). - Msg("Content of event that failed to parse") continue } if !explodeOnError { diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 7ffbb14..5555566 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -257,7 +257,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn Name: ptr.Ptr(info.Name), Topic: ptr.Ptr(info.Topic), Members: &bridgev2.ChatMemberList{ - IsFull: !info.IsIncognito && !info.IsParent, + IsFull: !info.IsIncognito, TotalMemberCount: len(info.Participants), MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)), PowerLevels: &bridgev2.PowerLevelOverrides{ @@ -284,15 +284,11 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn }, ExtraUpdates: extraUpdater, } - var hasSelf bool for _, pcp := range info.Participants { member := bridgev2.ChatMember{ EventSender: wa.makeEventSender(ctx, pcp.JID), Membership: event.MembershipJoin, } - if member.EventSender.IsFromMe { - hasSelf = true - } if pcp.IsSuperAdmin { member.PowerLevel = ptr.Ptr(superAdminPL) } else if pcp.IsAdmin { @@ -303,21 +299,18 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn member.MemberEventExtra = map[string]any{ "com.beeper.exclude_from_timeline": true, } - wrapped.Members.MemberMap.Set(member) + wrapped.Members.MemberMap[waid.MakeUserID(pcp.JID)] = member if pcp.JID.Server == types.HiddenUserServer && !pcp.PhoneNumber.IsEmpty() { - wrapped.Members.MemberMap.Add(bridgev2.ChatMember{ + wrapped.Members.MemberMap[waid.MakeUserID(pcp.PhoneNumber)] = bridgev2.ChatMember{ EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(pcp.PhoneNumber)}, Membership: event.MembershipLeave, PrevMembership: event.MembershipJoin, MemberEventExtra: map[string]any{ "com.beeper.exclude_from_timeline": true, }, - }) + } } } - if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID { - wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.Device.LID)}) - } if !info.LinkedParentJID.IsEmpty() { wrapped.ParentID = ptr.Ptr(waid.MakePortalID(info.LinkedParentJID)) @@ -457,7 +450,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), Get: func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithOnlyPath(ctx, avatar.DirectPath) + return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") }, } } @@ -496,7 +489,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne if info.ThreadMeta.Picture != nil { avatar.ID = networkid.AvatarID(info.ThreadMeta.Picture.ID) avatar.Get = func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithOnlyPath(ctx, info.ThreadMeta.Picture.DirectPath) + return wa.Client.DownloadMediaWithPath(ctx, info.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") } } else if info.ThreadMeta.Preview.ID != "" { avatar.ID = networkid.AvatarID(info.ThreadMeta.Preview.ID) @@ -507,7 +500,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne } else if meta.ThreadMeta.Picture == nil { return nil, fmt.Errorf("full res avatar info is missing") } - return wa.Client.DownloadMediaWithOnlyPath(ctx, meta.ThreadMeta.Picture.DirectPath) + return wa.Client.DownloadMediaWithPath(ctx, meta.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") } } else { avatar.ID = "remove" diff --git a/pkg/connector/config.go b/pkg/connector/config.go index e51c80a..2445647 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -2,7 +2,6 @@ package connector import ( _ "embed" - "fmt" "strings" "text/template" "time" @@ -91,15 +90,7 @@ func (c *Config) UnmarshalYAML(node *yaml.Node) error { func (c *Config) PostProcess() error { var err error c.displaynameTemplate, err = template.New("displayname").Parse(c.DisplaynameTemplate) - if err != nil { - return err - } - // Try to execute template to make sure it's valid - _, err = c.formatDisplayname(types.PSAJID, "", types.ContactInfo{}) - if err != nil { - return fmt.Errorf("failed to execute displayname template: %w", err) - } - return nil + return err } func upgradeConfig(helper up.Helper) { @@ -160,7 +151,7 @@ type DisplaynameParams struct { Short string } -func (c *Config) formatDisplayname(jid types.JID, phone string, contact types.ContactInfo) (string, error) { +func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { var nameBuf strings.Builder if phone == "" && jid.Server == types.DefaultUserServer { phone = "+" + jid.User @@ -179,21 +170,13 @@ func (c *Config) formatDisplayname(jid types.JID, phone string, contact types.Co Name: contact.FullName, Short: contact.FirstName, }) - return nameBuf.String(), err -} - -func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { - name, err := c.formatDisplayname(jid, phone, contact) if err != nil { panic(err) } - return name + return nameBuf.String() } func redactPhone(phone string) string { - if len(phone) <= 4 { - return phone - } // This doesn't keep 2+ digit country codes properly, but whatever return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:] } diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 89a54fc..ac2fbd2 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -17,7 +17,6 @@ package connector import ( - "bytes" "context" "encoding/json" "errors" @@ -131,7 +130,9 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars } return &mediaproxy.GetMediaResponseFile{ Callback: func(w *os.File) (*mediaproxy.FileMeta, error) { - return &mediaproxy.FileMeta{}, waClient.Client.DownloadMediaWithOnlyPathToFile(ctx, cachedInfo.DirectPath, w) + return &mediaproxy.FileMeta{}, waClient.Client.DownloadMediaWithPathToFile( + ctx, cachedInfo.DirectPath, nil, nil, nil, 0, "", "", w, + ) }, }, nil } @@ -170,9 +171,6 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par if err != nil { return nil, fmt.Errorf("failed to unmarshal media keys: %w", err) } - if version := parsedID.Message.Version; len(version) > 0 && !bytes.Equal(version, keys.EncSHA256) && !bytes.Equal(version, keys.SHA256) { - return nil, mautrix.MNotFound.WithMessage("Version mismatch, media was likely replaced") - } var ul *bridgev2.UserLogin if parsedID.UserLogin != "" { ul = wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin) @@ -225,7 +223,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse( log.Trace().Msg("Retrying download after successful retry") err = waClient.Client.DownloadToFile(ctx, keys, f) } - if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return nil, err diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 411c39e..de5bdb8 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -91,7 +91,6 @@ type WAMessageEvent struct { parsedMessageType string isUndecryptableUpsertSubEvent bool - dontRenderEdited bool postHandle func() } @@ -182,15 +181,12 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } var editedMsg *waE2E.Message var previouslyConvertedPart *bridgev2.ConvertedMessagePart - targetMessage := evt.GetTargetMessage() - cacheMessage := targetMessage if evt.isUndecryptableUpsertSubEvent { // TODO db metadata needs to be updated in this case to remove the error editedMsg = evt.Message - cacheMessage = evt.GetID() } else { editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage() - previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, targetMessage) + previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, evt.GetTargetMessage()) meta := existing[0].Metadata.(*waid.MessageMetadata) if slices.Contains(meta.Edits, evt.Info.ID) { return nil, fmt.Errorf("%w: edit already handled", bridgev2.ErrIgnoringRemoteEvent) @@ -206,11 +202,9 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) } - } else if len(cm.Parts) > 0 && cacheMessage != "" { - evt.wa.Main.AddMediaEditCache(portal, cacheMessage, cm.Parts[0]) } editPart := cm.Parts[0].ToEditPart(existing[0]) - if evt.isUndecryptableUpsertSubEvent || evt.dontRenderEdited { + if evt.isUndecryptableUpsertSubEvent { if editPart.TopLevelExtra == nil { editPart.TopLevelExtra = make(map[string]any) } diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 299449f..b0963ec 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -48,12 +48,11 @@ var ( _ bridgev2.DeleteChatHandlingNetworkAPI = (*WhatsAppClient)(nil) ) -func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (result *bridgev2.MatrixMessageResponse, retErr error) { +func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { waMsg, optionMap, err := wa.Main.MsgConv.PollStartToWhatsApp(ctx, msg.Content, msg.ReplyTo, msg.Portal) if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } - defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) if err != nil { return nil, err @@ -68,21 +67,19 @@ func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridge return resp, nil } -func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev2.MatrixPollVote) (result *bridgev2.MatrixMessageResponse, retErr error) { +func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev2.MatrixPollVote) (*bridgev2.MatrixMessageResponse, error) { waMsg, err := wa.Main.MsgConv.PollVoteToWhatsApp(ctx, wa.Client, msg.Content, msg.VoteTo) if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } - defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) } -func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (result *bridgev2.MatrixMessageResponse, retErr error) { +func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) { waMsg, req, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, msg.Event, msg.Content, msg.ReplyTo, msg.ThreadRoot, msg.Portal) if err != nil { return nil, fmt.Errorf("failed to convert message: %w", err) } - defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req) } @@ -157,7 +154,7 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge }, nil } -func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (result *database.Reaction, retErr error) { +func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (*database.Reaction, error) { messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { return nil, fmt.Errorf("failed to parse target message ID: %w", err) @@ -174,7 +171,6 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev SenderTimestampMS: proto.Int64(msg.Event.Timestamp), }, } - defer wa.mcTrack(msg, time.Now(), &retErr) var req whatsmeow.SendRequestExtra if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) @@ -196,7 +192,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev }, err } -func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error { messageID, err := waid.ParseMessageID(msg.TargetReaction.MessageID) if err != nil { return fmt.Errorf("failed to parse target message ID: %w", err) @@ -220,13 +216,12 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b extra.ID = types.MessageID(msg.InputTransactionID) } - defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg, extra) zerolog.Ctx(ctx).Trace().Any("response", resp).Msg("WhatsApp reaction response") return err } -func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) error { log := zerolog.Ctx(ctx) var editID types.MessageID @@ -250,8 +245,6 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M if err != nil { return fmt.Errorf("failed to convert message: %w", err) } - defer wa.mcTrack(edit, time.Now(), &retErr) - convertedEdit := wa.Client.BuildEdit(messageID.Chat, messageID.ID, waMsg) if edit.OrigSender == nil { convertedEdit.EditedMessage.Message.ProtocolMessage.TimestampMS = proto.Int64(edit.Event.Timestamp) @@ -266,7 +259,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M return err } -func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error { log := zerolog.Ctx(ctx) messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { @@ -278,7 +271,6 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br return fmt.Errorf("failed to parse portal ID: %w", err) } - defer wa.mcTrack(msg, time.Now(), &retErr) revokeMessage := wa.Client.BuildRevoke(messageID.Chat, messageID.Sender, messageID.ID) extra := whatsmeow.SendRequestExtra{} @@ -291,7 +283,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br return err } -func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error { if !receipt.ReadUpTo.After(receipt.LastRead) { return nil } @@ -330,7 +322,6 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * } messagesToRead[key] = append(messagesToRead[key], parsed.ID) } - defer wa.mcTrack(receipt, time.Now(), &retErr) for messageSender, ids := range messagesToRead { err = wa.Client.MarkRead(ctx, ids, receipt.Receipt.Timestamp, portalJID, messageSender) if err != nil { @@ -340,7 +331,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * return err } -func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) error { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -361,7 +352,6 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. return nil } - defer wa.mcTrack(msg, time.Now(), &retErr) if wa.Main.Config.SendPresenceOnTyping { err = wa.updatePresence(ctx, types.PresenceAvailable) if err != nil { @@ -373,7 +363,7 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. var errUnsupportedDisappearingTimer = bridgev2.WrapErrorInStatus(errors.New("invalid value for disappearing timer")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true) -func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (ok bool, retErr error) { +func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (bool, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -385,7 +375,6 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg return false, fmt.Errorf("%w (%s)", errUnsupportedDisappearingTimer, msg.Content.Timer.Duration) } - defer wa.mcTrack(msg, time.Now(), &retErr) settingTS := time.UnixMilli(msg.Event.Timestamp) err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS) if err != nil { @@ -402,7 +391,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg return true, nil } -func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (result *bridgev2.MatrixMembershipResult, retErr error) { +func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) { if msg.Type.IsSelf && msg.OrigSender != nil { return nil, nil } @@ -446,7 +435,6 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return nil, fmt.Errorf("cannot get target intent: unknown type: %T", target) } - defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) if err != nil { return nil, err @@ -462,7 +450,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return &bridgev2.MatrixMembershipResult{RedirectTo: waid.MakeUserID(resp[0].JID)}, nil } -func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (ok bool, retErr error) { +func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (bool, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -472,7 +460,6 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev return false, fmt.Errorf("cannot set room name for DM") } - defer wa.mcTrack(msg, time.Now(), &retErr) err = wa.Client.SetGroupName(ctx, portalJID, msg.Content.Name) if err != nil { return false, err @@ -484,7 +471,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev return true, nil } -func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.MatrixRoomTopic) (ok bool, retErr error) { +func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.MatrixRoomTopic) (bool, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -494,7 +481,6 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge return false, fmt.Errorf("cannot set room topic for DM") } - defer wa.mcTrack(msg, time.Now(), &retErr) newID := wa.Client.GenerateMessageID() oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID err = wa.Client.SetGroupTopic(ctx, portalJID, oldID, newID, msg.Content.Topic) @@ -509,7 +495,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge return true, nil } -func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2.MatrixRoomAvatar) (ok bool, retErr error) { +func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2.MatrixRoomAvatar) (bool, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -532,7 +518,6 @@ func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridg } } - defer wa.mcTrack(msg, time.Now(), &retErr) avatarID, err := wa.Client.SetGroupPhoto(ctx, portalJID, data) if err != nil { return false, err @@ -600,7 +585,7 @@ func convertRoomAvatar(data []byte) ([]byte, error) { return buf.Bytes(), nil } -func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMute) (retErr error) { +func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMute) error { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -611,16 +596,14 @@ func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMu if !muted || mutedUntil == event.MutedForever { muteTS = nil } - defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildMuteAbs(chatJID, muted, muteTS)) } -func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.MatrixRoomTag) (retErr error) { +func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.MatrixRoomTag) error { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err } - defer wa.mcTrack(msg, time.Now(), &retErr) _, isFavorite := msg.Content.Tags[event.RoomTagFavourite] return wa.Client.SendAppState(ctx, appstate.BuildPin(chatJID, isFavorite)) } @@ -652,7 +635,7 @@ func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types. return lastTS, lastKey, nil } -func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) (retErr error) { +func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) error { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -661,11 +644,10 @@ func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2. if err != nil { return err } - defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildMarkChatAsRead(chatJID, msg.Content.Unread, lastTS, lastKey)) } -func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) (retErr error) { +func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) error { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -686,6 +668,5 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } - defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true)) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2b9b145..3abcede 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -28,6 +28,7 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" "maunium.net/go/mautrix/bridgev2" @@ -76,7 +77,6 @@ func init() { func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { log := wa.UserLogin.Log ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) - wa.MC.OnWhatsAppEvent(rawEvt) success = true switch evt := rawEvt.(type) { @@ -126,20 +126,6 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWANewsletterLeave(evt) case *events.Picture: success = wa.handleWAPictureUpdate(ctx, evt) - case *events.NotifyAccountReachoutTimelock: - wa.UserLogin.TrackAnalytics("WhatsApp Account Reachout Timelock", map[string]any{ - "enforcement_type": evt.EnforcementType, - "is_active": evt.IsActive, - "time_enforcement_ends": evt.TimeEnforcementEnds.Time, - }) - wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil = evt.TimeEnforcementEnds.Time - if wa.UserLogin.BridgeState.GetPrevUnsent().StateEvent == status.StateConnected { - wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) - } - err := wa.UserLogin.Save(ctx) - if err != nil { - log.Err(err).Msg("Failed to save user login metadata after reachout timelock update") - } case *events.AppStateSyncComplete: wa.handleWAAppStateSyncComplete(ctx, evt) @@ -182,6 +168,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { }() go wa.syncRemoteProfile(ctx, nil) } + wa.MC.OnConnect(store.GetWAVersion()[2], wa.Device.Platform) case *events.OfflineSyncPreview: log.Info(). Int("message_count", evt.Messages). @@ -372,32 +359,24 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return } - dontRenderEdited := false messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation() if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD { parentKey := messageAssoc.GetParentMessageKey() - protocolMsg := evt.Message.GetProtocolMessage() - if protocolMsg.GetType() != waE2E.ProtocolMessage_MESSAGE_EDIT || protocolMsg.GetKey() == nil { - protocolMsg = &waE2E.ProtocolMessage{ - Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), - Key: parentKey, - EditedMessage: evt.Message.GetAssociatedChildMessage().GetMessage(), - } - dontRenderEdited = true - } else if child := protocolMsg.GetEditedMessage().GetAssociatedChildMessage().GetMessage(); child != nil { - protocolMsg.EditedMessage = child - protocolMsg.Key = parentKey - } + associatedMessage := evt.Message.GetAssociatedChildMessage().GetMessage() wa.UserLogin.Log.Debug(). Str("message_id", evt.Info.ID). Str("parent_id", parentKey.GetID()). Stringer("assoc_type", assocType). Msg("Received HD replacement message, converting to edit") + protocolMsg := &waE2E.ProtocolMessage{ + Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), + Key: parentKey, + EditedMessage: associatedMessage, + } evt.Message = &waE2E.Message{ ProtocolMessage: protocolMsg, } - parsedMessageType = getMessageType(evt.Message) } else if assocType == waE2E.MessageAssociation_MOTION_PHOTO { //evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() wa.UserLogin.Log.Debug(). @@ -416,7 +395,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa MsgEvent: evt, parsedMessageType: parsedMessageType, - dontRenderEdited: dontRenderEdited, }) return res.Success } diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 5da419f..88e7b75 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -2,7 +2,6 @@ package connector import ( "context" - "encoding/json" "errors" "fmt" "net/http" @@ -13,7 +12,6 @@ import ( "go.mau.fi/util/exsync" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" - "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" "maunium.net/go/mautrix/bridgev2" @@ -27,7 +25,6 @@ const ( LoginStepIDQR = "fi.mau.whatsapp.login.qr" LoginStepIDPhoneNumber = "fi.mau.whatsapp.login.phone" LoginStepIDCode = "fi.mau.whatsapp.login.code" - LoginStepIDPasskey = "fi.mau.whatsapp.login.passkey" LoginStepIDComplete = "fi.mau.whatsapp.login.complete" LoginFlowIDQR = "qr" @@ -79,8 +76,8 @@ var ( } ErrRateLimitedByWhatsApp = bridgev2.RespError{ ErrCode: "FI.MAU.WHATSAPP.RATE_LIMITED", - Err: "Rate limited by WhatsApp. Try again later.", - StatusCode: http.StatusBadRequest, + Err: "Rate limited by WhatsApp", + StatusCode: http.StatusTooManyRequests, } ) @@ -94,11 +91,9 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, Bool("phone_code", flowID == LoginFlowIDPhone). Logger(), - WaitForQRs: exsync.NewEvent(), - LoginComplete: exsync.NewEvent(), - PasskeyRequest: exsync.NewEvent(), - PasskeyConfirmation: exsync.NewEvent(), - Received515: exsync.NewEvent(), + WaitForQRs: exsync.NewEvent(), + LoginComplete: exsync.NewEvent(), + Received515: exsync.NewEvent(), }, nil } @@ -119,11 +114,6 @@ type WALogin struct { Received515 *exsync.Event PrevQRIndex atomic.Int32 - PasskeyRequest *exsync.Event - PasskeyRequestData *events.PairPasskeyRequest - PasskeyConfirmation *exsync.Event - PasskeyConfirmationData *events.PairPasskeyConfirmation - Closed atomic.Bool EventHandlerID uint32 } @@ -132,7 +122,6 @@ var ( _ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil) _ bridgev2.LoginProcessUserInput = (*WALogin)(nil) _ bridgev2.LoginProcessWithOverride = (*WALogin)(nil) - _ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil) ) const LoginConnectWait = 15 * time.Second @@ -273,17 +262,6 @@ func (wl *WALogin) handleEvent(rawEvt any) { case *events.ClientOutdated: wl.Log.Error().Msg("Got client outdated error") wl.LoginError = ErrLoginClientOutdated - case *events.PairPasskeyRequest: - wl.PasskeyRequestData = evt - wl.PasskeyRequest.Set() - return - case *events.PairPasskeyConfirmation: - wl.PasskeyConfirmationData = evt - wl.PasskeyConfirmation.Set() - return - case *events.PairPasskeyError: - wl.Log.Err(evt.Error).Msg("Got passkey error") - wl.LoginError = evt.Error case *events.PairSuccess: wl.Log.Info().Any("event_data", evt).Msg("Got pair successful event") wl.LoginSuccess = evt @@ -311,14 +289,10 @@ func (wl *WALogin) handleEvent(rawEvt any) { func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.PhoneCode { - select { - case <-ctx.Done(): + err := wl.LoginComplete.Wait(ctx) + if err != nil { wl.Cancel() - return nil, ctx.Err() - case <-wl.PasskeyRequest.GetChan(): - return wl.makePasskeyStep() - case <-wl.LoginComplete.GetChan(): - // continue + return nil, err } } else { prevIndex := int(wl.PrevQRIndex.Load()) @@ -345,16 +319,9 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { case <-ctx.Done(): wl.Cancel() return nil, ctx.Err() - case <-wl.PasskeyRequest.GetChan(): - return wl.makePasskeyStep() case <-wl.LoginComplete.GetChan(): - // continue } } - return wl.onLoginComplete(ctx) -} - -func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.LoginError != nil { wl.Log.Debug().Err(wl.LoginError).Msg("Login completed with error") wl.Cancel() @@ -405,66 +372,6 @@ func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, er }, nil } -func (wl *WALogin) makePasskeyStep() (*bridgev2.LoginStep, error) { - pubkeyData, err := json.Marshal(wl.PasskeyRequestData.PublicKey) - if err != nil { - wl.Log.Err(err).Msg("Failed to marshal public key data") - return nil, err - } - return &bridgev2.LoginStep{ - Type: bridgev2.LoginStepTypeWebAuthn, - StepID: LoginStepIDPasskey, - WebAuthnParams: &bridgev2.LoginWebAuthnParams{ - URL: "https://web.whatsapp.com", - PublicKey: pubkeyData, - }, - }, nil -} - -func (wl *WALogin) SubmitWebAuthnResponse(ctx context.Context, rawResp json.RawMessage) (*bridgev2.LoginStep, error) { - var resp types.WebAuthnResponse - err := json.Unmarshal(rawResp, &resp) - if err != nil { - wl.Log.Err(err).Msg("Failed to unmarshal WebAuthn response") - wl.Cancel() - return nil, err - } - err = wl.Client.SendPasskeyResponse(ctx, &resp) - if err != nil { - wl.Log.Err(err).Msg("Failed to send WebAuthn response") - wl.Cancel() - return nil, err - } - select { - case <-ctx.Done(): - wl.Cancel() - return nil, ctx.Err() - case <-wl.PasskeyConfirmation.GetChan(): - if !wl.PasskeyConfirmationData.SkipHandoffUX { - wl.Cancel() - return nil, bridgev2.RespError{ - ErrCode: "FI.MAU.WHATSAPP.NOT_IMPLEMENTED", - Err: "Displaying WebAuthn pairing confirmation codes is not yet implemented", - StatusCode: http.StatusBadRequest, - } - } - err = wl.Client.SendPasskeyConfirmation(ctx) - if err != nil { - wl.Log.Err(err).Msg("Failed to send WebAuthn confirmation") - wl.Cancel() - return nil, err - } - case <-wl.LoginComplete.GetChan(): - } - select { - case <-ctx.Done(): - wl.Cancel() - return nil, ctx.Err() - case <-wl.LoginComplete.GetChan(): - } - return wl.onLoginComplete(ctx) -} - func (wl *WALogin) Cancel() { wl.Closed.Store(true) wl.Client.RemoveEventHandler(wl.EventHandlerID) diff --git a/pkg/connector/mclient.go b/pkg/connector/mclient.go index f85545a..0930617 100644 --- a/pkg/connector/mclient.go +++ b/pkg/connector/mclient.go @@ -21,7 +21,6 @@ import ( "encoding/json" "time" - "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" waBinary "go.mau.fi/whatsmeow/binary" "go.mau.fi/whatsmeow/types" @@ -40,16 +39,14 @@ func (wa *WhatsAppClient) initMC() { } type mClient = interface { - OnMatrixEvent(any, time.Duration, error) - OnWhatsAppEvent(any) + OnConnect(version uint32, platform string) } type noopMC struct{} var noopMCInstance mClient = &noopMC{} -func (n *noopMC) OnMatrixEvent(any, time.Duration, error) {} -func (n *noopMC) OnWhatsAppEvent(any) {} +func (n *noopMC) OnConnect(version uint32, platform string) {} type mWAClient = interface { MSend(data []byte) @@ -82,7 +79,3 @@ func (wa *WhatsAppClient) MSave(s json.RawMessage) { wa.UserLogin.Log.Err(err).Msg("Failed to save MC data") } } - -func (wa *WhatsAppClient) mcTrack(evt any, start time.Time, err *error) { - wa.MC.OnMatrixEvent(evt, time.Since(start), ptr.Val(err)) -} diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index 2686250..2c382d1 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -70,9 +70,10 @@ func (wa *WhatsAppClient) processFailedMedia(ctx context.Context, portalKey netw func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) { log := wa.UserLogin.Log.With().Str("loop", "media requests").Logger() ctx = log.WithContext(ctx) - userTz, err := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoadTimezone() + tzName := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).Timezone + userTz, err := time.LoadLocation(tzName) var startIn time.Duration - if err == nil && userTz != nil { + if tzName != "" && err == nil && userTz != nil { now := time.Now() startAt := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, userTz) startAt = startAt.Add(time.Duration(wa.Main.Config.HistorySync.MediaRequests.RequestLocalTime) * time.Minute) diff --git a/pkg/connector/phoneping.go b/pkg/connector/phoneping.go index 71094c9..ce38079 100644 --- a/pkg/connector/phoneping.go +++ b/pkg/connector/phoneping.go @@ -37,13 +37,6 @@ func (wa *WhatsAppClient) FillBridgeState(state status.BridgeState) status.Bridg state.Error = WAPhoneOffline state.UserAction = status.UserActionOpenNative } - rtu := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil - if !rtu.IsZero() { - if state.Info == nil { - state.Info = make(map[string]any) - } - state.Info["reachout_timelock_until"] = rtu - } return state } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index e827955..aa84938 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -29,8 +29,7 @@ var ResyncLoopInterval = 4 * time.Hour var ResyncJitterSeconds = 3600 func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { - lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time - if lastSync.Add(ResyncMinInterval).After(time.Now()) { + if ghost.Metadata.(*waid.GhostMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -44,7 +43,6 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { wa.UserLogin.Log.Debug(). Stringer("jid", jid). Str("next_resync_in", nextResyncIn). - Time("last_ghost_resync", lastSync). Msg("Enqueued resync for ghost") } wa.resyncQueueLock.Unlock() @@ -52,8 +50,7 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) { jid, _ := waid.ParsePortalID(portal.ID) - lastSync := portal.Metadata.(*waid.PortalMetadata).LastSync.Time - if lastSync.Add(ResyncMinInterval).After(time.Now()) { + if portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { return } else if !allowDM && jid.Server != types.GroupServer { return @@ -64,7 +61,6 @@ func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM b wa.UserLogin.Log.Debug(). Stringer("jid", jid). Stringer("next_resync_in", time.Until(wa.nextResync)). - Time("last_portal_resync", lastSync). Msg("Enqueued resync for portal") } wa.resyncQueueLock.Unlock() @@ -361,7 +357,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), Get: func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithOnlyPath(ctx, avatar.DirectPath) + return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") }, } } diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 4bcf483..92e7f86 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -130,8 +130,8 @@ func getMessageType(waMsg *waE2E.Message) string { return "secret encrypted" case waMsg.PollResultSnapshotMessage != nil: return "poll result snapshot" - case waMsg.MessageHistoryNotice != nil: - return "message history notice" + case waMsg.MessageHistoryBundle != nil: + return "message history bundle" case waMsg.RequestPhoneNumberMessage != nil: return "request phone number" case waMsg.KeepInChatMessage != nil: @@ -152,7 +152,7 @@ func getMessageType(waMsg *waE2E.Message) string { return "chat" case waMsg.PlaceholderMessage != nil: return "placeholder" - case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil, waMsg.MessageHistoryBundle != nil: + case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil: return "ignore" default: return "unknown" diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index f0e4560..11569e2 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -215,8 +215,6 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPlaceholderMessage(ctx, waMsg) case waMsg.GroupInviteMessage != nil: part, contextInfo = mc.convertGroupInviteMessage(ctx, info, waMsg.GroupInviteMessage) - case waMsg.MessageHistoryNotice != nil: - part, contextInfo = mc.convertMessageHistoryNotice(ctx, info, waMsg.MessageHistoryNotice) case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waE2E.ProtocolMessage_EPHEMERAL_SETTING: part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill) case waMsg.EncCommentMessage != nil: diff --git a/pkg/msgconv/wa-location.go b/pkg/msgconv/wa-location.go index 4a1e043..17efecd 100644 --- a/pkg/msgconv/wa-location.go +++ b/pkg/msgconv/wa-location.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "fmt" - "html" "image" "math" "net/http" @@ -52,7 +51,7 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE MsgType: event.MsgLocation, Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url), Format: event.FormatHTML, - FormattedBody: fmt.Sprintf(`Location: %s
%s`, html.EscapeString(url), html.EscapeString(name), html.EscapeString(msg.GetAddress())), + FormattedBody: fmt.Sprintf("Location: %s
%s", url, name, msg.GetAddress()), GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()), } diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 9964800..c2a8624 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -27,7 +27,6 @@ import ( "strings" "github.com/rs/zerolog" - "go.mau.fi/util/exfmt" "go.mau.fi/util/exmime" "go.mau.fi/util/exslices" "go.mau.fi/whatsmeow" @@ -91,7 +90,7 @@ func (mc *MessageConverter) convertMediaMessage( var err error portal := getPortal(ctx) idOverride := getEditTargetID(ctx) - preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver, getMediaIDVersion(msg))) + preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver)) if err != nil { panic(fmt.Errorf("failed to generate content URI: %w", err)) } @@ -120,20 +119,13 @@ func (mc *MessageConverter) convertMediaMessage( return } -func getMediaIDVersion(msg MediaMessage) []byte { - if encSHA256 := msg.GetFileEncSHA256(); len(encSHA256) > 0 { - return encSHA256 - } - return msg.GetFileSHA256() -} - func (mc *MessageConverter) convertAlbumMessage(ctx context.Context, msg *waE2E.AlbumMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { parts := make([]string, 0, 2) if msg.GetExpectedImageCount() > 0 { - parts = append(parts, exfmt.Pluralizable("image")(int(msg.GetExpectedImageCount()))) + parts = append(parts, fmt.Sprintf("%d images", msg.GetExpectedImageCount())) } if msg.GetExpectedVideoCount() > 0 { - parts = append(parts, exfmt.Pluralizable("video")(int(msg.GetExpectedVideoCount()))) + parts = append(parts, fmt.Sprintf("%d videos", msg.GetExpectedVideoCount())) } var partDesc string if len(parts) > 0 { @@ -375,7 +367,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( var err error part.URL, part.File, err = intent.UploadMediaStream(ctx, roomID, -1, true, func(file io.Writer) (*bridgev2.FileStreamResult, error) { err := client.DownloadToFile(ctx, message, file.(*os.File)) - if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return nil, fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err) @@ -396,7 +388,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( } } else { data, err := client.Download(ctx, message) - if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err) diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index fa495e0..7ca4427 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -20,14 +20,12 @@ import ( "context" "encoding/base64" "fmt" - "html" "html/template" "strings" "time" "github.com/rs/zerolog" "go.mau.fi/util/exerrors" - "go.mau.fi/util/exfmt" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/proto/waAICommonDeprecated" "go.mau.fi/whatsmeow/proto/waE2E" @@ -102,7 +100,7 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info } } - htmlMessage := fmt.Sprintf(template, event.TextToHTML(msg.GetCaption()), html.EscapeString(msg.GetGroupName()), expiry, mc.Bridge.Config.CommandPrefix) + htmlMessage := fmt.Sprintf(template, event.TextToHTML(msg.GetCaption()), msg.GetGroupName(), expiry, mc.Bridge.Config.CommandPrefix) content := &event.MessageEventContent{ MsgType: event.MsgText, Body: format.HTMLToText(htmlMessage), @@ -119,92 +117,6 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info }, msg.GetContextInfo() } -func (mc *MessageConverter) formatMessageHistoryNoticeJID(ctx context.Context, jid types.JID) string { - _, displayName, err := mc.getBasicUserInfo(ctx, jid) - if err != nil { - zerolog.Ctx(ctx).Err(err).Stringer("jid", jid).Msg("Failed to get user info for message history notice") - } else if displayName != "" { - return displayName - } - switch jid.Server { - case types.DefaultUserServer: - return "+" + jid.User - default: - return "Unknown user " + jid.String() - } -} - -const maxMessageHistoryNoticeReceivers = 5 - -var others = exfmt.Pluralizable("other") - -func (mc *MessageConverter) formatMessageHistoryNoticeReceivers(ctx context.Context, receivers []string) string { - receiverLimit := min(len(receivers), maxMessageHistoryNoticeReceivers) - receiverNames := make([]string, 0, receiverLimit) - for _, receiver := range receivers[:receiverLimit] { - jid, err := types.ParseJID(receiver) - if err != nil { - zerolog.Ctx(ctx).Err(err).Str("receiver", receiver).Msg("Failed to parse message history receiver JID") - receiverNames = append(receiverNames, receiver) - } else { - receiverNames = append(receiverNames, mc.formatMessageHistoryNoticeJID(ctx, jid)) - } - } - receiverText := strings.Join(receiverNames, ", ") - if len(receivers) > receiverLimit { - receiverText = fmt.Sprintf("%s + %s", receiverText, others(len(receivers)-receiverLimit)) - } - return receiverText -} - -func (mc *MessageConverter) messageHistoryNoticeLocation(ctx context.Context) *time.Location { - portal := getPortal(ctx) - loginID := portal.Receiver - if loginID == "" { - loginID = waid.MakeUserLoginID(getClient(ctx).Store.GetJID().ToNonAD()) - } - if login := mc.Bridge.GetCachedUserLoginByID(loginID); login != nil { - meta, _ := login.Metadata.(*waid.UserLoginMetadata) - loc, err := meta.LoadTimezone() - if err != nil { - zerolog.Ctx(ctx).Err(err).Str("timezone", meta.Timezone).Msg("Failed to load user timezone for message history notice") - } else if loc != nil { - return loc - } - } - return time.Local -} - -func (mc *MessageConverter) convertMessageHistoryNotice(ctx context.Context, info *types.MessageInfo, msg *waE2E.MessageHistoryNotice) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { - metadata := msg.GetMessageHistoryMetadata() - - sender := mc.formatMessageHistoryNoticeJID(ctx, info.Sender) - body := fmt.Sprintf("%s sent message history", sender) - if receiverText := mc.formatMessageHistoryNoticeReceivers(ctx, metadata.GetHistoryReceivers()); receiverText != "" { - body = fmt.Sprintf("%s to %s", body, receiverText) - } - if count := metadata.GetMessageCount(); count > 0 { - messageWord := "messages" - if count == 1 { - messageWord = "message" - } - body = fmt.Sprintf("%s (%d %s)", body, count, messageWord) - } - if metadata != nil && metadata.OldestMessageTimestampInWindow != nil { - oldestTS := time.Unix(metadata.GetOldestMessageTimestampInWindow(), 0).In(mc.messageHistoryNoticeLocation(ctx)) - body = fmt.Sprintf("%s, starting %s", body, oldestTS.Format("Jan 2, 2006 at 3:04 PM")) - } - body += "." - - return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: body, - }, - }, msg.GetContextInfo() -} - func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage, ts time.Time, isBackfill bool) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { portal := getPortal(ctx) portalMeta := portal.Metadata.(*waid.PortalMetadata) diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 1e54426..2785105 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -41,8 +41,7 @@ type UserLoginMetadata struct { AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` - HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` - ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` MData json.RawMessage `json:"mdata,omitempty"` } @@ -62,13 +61,6 @@ func (m *UserLoginMetadata) GeneratePushKeys() { } } -func (m *UserLoginMetadata) LoadTimezone() (*time.Location, error) { - if m == nil || m.Timezone == "" { - return nil, nil - } - return time.LoadLocation(m.Timezone) -} - type MessageErrorType string const ( diff --git a/pkg/waid/id.go b/pkg/waid/id.go index e522f5f..840e0f1 100644 --- a/pkg/waid/id.go +++ b/pkg/waid/id.go @@ -88,10 +88,9 @@ func MakeFakeMessageID(chat, sender types.JID, data string) networkid.MessageID } type ParsedMessageID struct { - Chat types.JID - Sender types.JID - ID types.MessageID - Version []byte + Chat types.JID + Sender types.JID + ID types.MessageID } func (pmi *ParsedMessageID) String() networkid.MessageID { diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 716e187..093ece1 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -36,7 +36,7 @@ const ( mediaIDTypeStickerPackItem = 252 ) -func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID, version []byte) networkid.MediaID { +func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID) networkid.MediaID { compactChat := compactJID(messageInfo.Chat.ToNonAD()) compactSender := compactJID(messageInfo.Sender.ToNonAD()) receiverID := compactJID(ParseUserLoginID(receiver, 0)) @@ -46,7 +46,7 @@ func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, rec } else { compactID = compactMsgID(messageInfo.ID) } - mediaID := make([]byte, 0, 6+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)+len(version)) + mediaID := make([]byte, 0, 5+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)) mediaID = append(mediaID, mediaIDTypeMessage) mediaID = append(mediaID, byte(len(compactChat))) mediaID = append(mediaID, compactChat...) @@ -56,8 +56,6 @@ func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, rec mediaID = append(mediaID, receiverID...) mediaID = append(mediaID, byte(len(compactID))) mediaID = append(mediaID, compactID...) - mediaID = append(mediaID, byte(len(version))) - mediaID = append(mediaID, version...) return mediaID } @@ -140,12 +138,6 @@ func ParseMediaID(mediaID networkid.MediaID) (*ParsedMediaID, error) { Sender: senderJID, ID: id, } - if len(mediaID) > 0 { - parsed.Message.Version, err = readCompact(&mediaID, rawBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse version: %w", err) - } - } parsed.UserLogin = MakeUserLoginID(receiverID) case mediaIDTypeAvatar, mediaIDTypeCommunityAvatar: targetJID, err := readCompact(&mediaID, parseCompactJID)