mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-07-07 14:53:20 -04:00
Compare commits
No commits in common. "main" and "v0.2605.0" have entirely different histories.
25 changed files with 285 additions and 436 deletions
|
|
@ -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
|
# v26.05
|
||||||
|
|
||||||
* Added support for importing sticker packs from WhatsApp.
|
* Added support for importing sticker packs from WhatsApp.
|
||||||
|
|
|
||||||
153
cmd/mautrix-whatsapp/legacyprovision.go
Normal file
153
cmd/mautrix-whatsapp/legacyprovision.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,12 +18,21 @@ var m = mxmain.BridgeMain{
|
||||||
Name: "mautrix-whatsapp",
|
Name: "mautrix-whatsapp",
|
||||||
URL: "https://github.com/mautrix/whatsapp",
|
URL: "https://github.com/mautrix/whatsapp",
|
||||||
Description: "A Matrix-WhatsApp puppeting bridge.",
|
Description: "A Matrix-WhatsApp puppeting bridge.",
|
||||||
Version: "26.06",
|
Version: "26.05",
|
||||||
SemCalVer: true,
|
SemCalVer: true,
|
||||||
Connector: &connector.WhatsAppConnector{},
|
Connector: &connector.WhatsAppConnector{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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.InitVersion(Tag, Commit, BuildTime)
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
go.mod
32
go.mod
|
|
@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp
|
||||||
|
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.26.4
|
toolchain go1.26.3
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
tool go.mau.fi/util/cmd/maubuild
|
||||||
|
|
||||||
|
|
@ -10,28 +10,28 @@ require (
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.35.1
|
||||||
github.com/tidwall/gjson v1.19.0
|
github.com/tidwall/gjson v1.19.0
|
||||||
go.mau.fi/util v0.9.10
|
go.mau.fi/util v0.9.9
|
||||||
go.mau.fi/webp v0.3.0
|
go.mau.fi/webp v0.2.0
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b
|
go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69
|
||||||
golang.org/x/image v0.42.0
|
golang.org/x/image v0.40.0
|
||||||
golang.org/x/net v0.56.0
|
golang.org/x/net v0.54.0
|
||||||
golang.org/x/sync v0.21.0
|
golang.org/x/sync v0.20.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
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 (
|
require (
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
github.com/beeper/argo-go v1.1.2 // 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/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // 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/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/rs/xid v1.6.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/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||||
github.com/yuin/goldmark v1.8.2 // 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
|
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.53.0 // indirect
|
golang.org/x/crypto v0.51.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect
|
||||||
golang.org/x/mod v0.37.0 // indirect
|
golang.org/x/mod v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
maunium.net/go/mauflag v1.0.0 // indirect
|
maunium.net/go/mauflag v1.0.0 // indirect
|
||||||
|
|
|
||||||
60
go.sum
60
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/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 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs=
|
||||||
github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4=
|
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.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
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 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
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=
|
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-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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||||
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
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 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
|
||||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
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=
|
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/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 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
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.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0=
|
||||||
go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0=
|
go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU=
|
||||||
go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg=
|
go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE=
|
||||||
go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A=
|
go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY=
|
||||||
go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo=
|
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b h1:ZUk1ErarDNpnbosXR/MeOz2gkqA4S1bh8zjaSRj7N+Y=
|
go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 h1:rRcG2I1LVQ11kH2+KSYxNeFNZXp1oKA5Yxmagsf5bks=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U=
|
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 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
||||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
|
||||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
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 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
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=
|
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 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
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.0 h1:vBakLzf8MAdfED3NzAKiMeKQbc3AQ4EAS03NC+TVMXQ=
|
||||||
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY=
|
maunium.net/go/mautrix v0.28.0/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0=
|
||||||
|
|
|
||||||
|
|
@ -260,9 +260,6 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
||||||
Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()).
|
Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()).
|
||||||
Msg("Dropping historical message due to parse error")
|
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
|
continue
|
||||||
}
|
}
|
||||||
if firstItemTime.IsZero() {
|
if firstItemTime.IsZero() {
|
||||||
|
|
@ -400,8 +397,8 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true)
|
wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true)
|
||||||
if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) {
|
if errors.Is(err, whatsmeow.ErrNotInGroup) {
|
||||||
log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID).
|
log.Debug().Stringer("chat_jid", conv.ChatJID).
|
||||||
Msg("Skipping creating room because the user is not a participant")
|
Msg("Skipping creating room because the user is not a participant")
|
||||||
//err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID)
|
//err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
|
|
@ -606,9 +603,6 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
Str("msg_id", msg.GetKey().GetID()).
|
Str("msg_id", msg.GetKey().GetID()).
|
||||||
Uint64("msg_time_seconds", msg.GetMessageTimestamp()).
|
Uint64("msg_time_seconds", msg.GetMessageTimestamp()).
|
||||||
Msg("Dropping historical message due to parse error")
|
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
|
continue
|
||||||
}
|
}
|
||||||
if !explodeOnError {
|
if !explodeOnError {
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
Name: ptr.Ptr(info.Name),
|
Name: ptr.Ptr(info.Name),
|
||||||
Topic: ptr.Ptr(info.Topic),
|
Topic: ptr.Ptr(info.Topic),
|
||||||
Members: &bridgev2.ChatMemberList{
|
Members: &bridgev2.ChatMemberList{
|
||||||
IsFull: !info.IsIncognito && !info.IsParent,
|
IsFull: !info.IsIncognito,
|
||||||
TotalMemberCount: len(info.Participants),
|
TotalMemberCount: len(info.Participants),
|
||||||
MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)),
|
MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)),
|
||||||
PowerLevels: &bridgev2.PowerLevelOverrides{
|
PowerLevels: &bridgev2.PowerLevelOverrides{
|
||||||
|
|
@ -284,15 +284,11 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
},
|
},
|
||||||
ExtraUpdates: extraUpdater,
|
ExtraUpdates: extraUpdater,
|
||||||
}
|
}
|
||||||
var hasSelf bool
|
|
||||||
for _, pcp := range info.Participants {
|
for _, pcp := range info.Participants {
|
||||||
member := bridgev2.ChatMember{
|
member := bridgev2.ChatMember{
|
||||||
EventSender: wa.makeEventSender(ctx, pcp.JID),
|
EventSender: wa.makeEventSender(ctx, pcp.JID),
|
||||||
Membership: event.MembershipJoin,
|
Membership: event.MembershipJoin,
|
||||||
}
|
}
|
||||||
if member.EventSender.IsFromMe {
|
|
||||||
hasSelf = true
|
|
||||||
}
|
|
||||||
if pcp.IsSuperAdmin {
|
if pcp.IsSuperAdmin {
|
||||||
member.PowerLevel = ptr.Ptr(superAdminPL)
|
member.PowerLevel = ptr.Ptr(superAdminPL)
|
||||||
} else if pcp.IsAdmin {
|
} else if pcp.IsAdmin {
|
||||||
|
|
@ -303,20 +299,17 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
member.MemberEventExtra = map[string]any{
|
member.MemberEventExtra = map[string]any{
|
||||||
"com.beeper.exclude_from_timeline": true,
|
"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() {
|
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)},
|
EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(pcp.PhoneNumber)},
|
||||||
Membership: event.MembershipLeave,
|
Membership: event.MembershipLeave,
|
||||||
PrevMembership: event.MembershipJoin,
|
PrevMembership: event.MembershipJoin,
|
||||||
MemberEventExtra: map[string]any{
|
MemberEventExtra: map[string]any{
|
||||||
"com.beeper.exclude_from_timeline": true,
|
"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() {
|
if !info.LinkedParentJID.IsEmpty() {
|
||||||
|
|
@ -457,7 +450,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types.
|
||||||
wrappedAvatar = &bridgev2.Avatar{
|
wrappedAvatar = &bridgev2.Avatar{
|
||||||
ID: networkid.AvatarID(avatar.ID),
|
ID: networkid.AvatarID(avatar.ID),
|
||||||
Get: func(ctx context.Context) ([]byte, error) {
|
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 {
|
if info.ThreadMeta.Picture != nil {
|
||||||
avatar.ID = networkid.AvatarID(info.ThreadMeta.Picture.ID)
|
avatar.ID = networkid.AvatarID(info.ThreadMeta.Picture.ID)
|
||||||
avatar.Get = func(ctx context.Context) ([]byte, error) {
|
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 != "" {
|
} else if info.ThreadMeta.Preview.ID != "" {
|
||||||
avatar.ID = networkid.AvatarID(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 {
|
} else if meta.ThreadMeta.Picture == nil {
|
||||||
return nil, fmt.Errorf("full res avatar info is missing")
|
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 {
|
} else {
|
||||||
avatar.ID = "remove"
|
avatar.ID = "remove"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -91,16 +90,8 @@ func (c *Config) UnmarshalYAML(node *yaml.Node) error {
|
||||||
func (c *Config) PostProcess() error {
|
func (c *Config) PostProcess() error {
|
||||||
var err error
|
var err error
|
||||||
c.displaynameTemplate, err = template.New("displayname").Parse(c.DisplaynameTemplate)
|
c.displaynameTemplate, err = template.New("displayname").Parse(c.DisplaynameTemplate)
|
||||||
if err != nil {
|
|
||||||
return err
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func upgradeConfig(helper up.Helper) {
|
func upgradeConfig(helper up.Helper) {
|
||||||
helper.Copy(up.Str, "os_name")
|
helper.Copy(up.Str, "os_name")
|
||||||
|
|
@ -160,7 +151,7 @@ type DisplaynameParams struct {
|
||||||
Short string
|
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
|
var nameBuf strings.Builder
|
||||||
if phone == "" && jid.Server == types.DefaultUserServer {
|
if phone == "" && jid.Server == types.DefaultUserServer {
|
||||||
phone = "+" + jid.User
|
phone = "+" + jid.User
|
||||||
|
|
@ -179,21 +170,13 @@ func (c *Config) formatDisplayname(jid types.JID, phone string, contact types.Co
|
||||||
Name: contact.FullName,
|
Name: contact.FullName,
|
||||||
Short: contact.FirstName,
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return name
|
return nameBuf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func redactPhone(phone string) string {
|
func redactPhone(phone string) string {
|
||||||
if len(phone) <= 4 {
|
|
||||||
return phone
|
|
||||||
}
|
|
||||||
// This doesn't keep 2+ digit country codes properly, but whatever
|
// This doesn't keep 2+ digit country codes properly, but whatever
|
||||||
return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:]
|
return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package connector
|
package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -131,7 +130,9 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars
|
||||||
}
|
}
|
||||||
return &mediaproxy.GetMediaResponseFile{
|
return &mediaproxy.GetMediaResponseFile{
|
||||||
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -170,9 +171,6 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal media keys: %w", err)
|
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
|
var ul *bridgev2.UserLogin
|
||||||
if parsedID.UserLogin != "" {
|
if parsedID.UserLogin != "" {
|
||||||
ul = wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin)
|
ul = wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin)
|
||||||
|
|
@ -225,7 +223,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
||||||
log.Trace().Msg("Retrying download after successful retry")
|
log.Trace().Msg("Retrying download after successful retry")
|
||||||
err = waClient.Client.DownloadToFile(ctx, keys, f)
|
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")
|
zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ type WAMessageEvent struct {
|
||||||
|
|
||||||
parsedMessageType string
|
parsedMessageType string
|
||||||
isUndecryptableUpsertSubEvent bool
|
isUndecryptableUpsertSubEvent bool
|
||||||
dontRenderEdited bool
|
|
||||||
postHandle func()
|
postHandle func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,15 +181,12 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por
|
||||||
}
|
}
|
||||||
var editedMsg *waE2E.Message
|
var editedMsg *waE2E.Message
|
||||||
var previouslyConvertedPart *bridgev2.ConvertedMessagePart
|
var previouslyConvertedPart *bridgev2.ConvertedMessagePart
|
||||||
targetMessage := evt.GetTargetMessage()
|
|
||||||
cacheMessage := targetMessage
|
|
||||||
if evt.isUndecryptableUpsertSubEvent {
|
if evt.isUndecryptableUpsertSubEvent {
|
||||||
// TODO db metadata needs to be updated in this case to remove the error
|
// TODO db metadata needs to be updated in this case to remove the error
|
||||||
editedMsg = evt.Message
|
editedMsg = evt.Message
|
||||||
cacheMessage = evt.GetID()
|
|
||||||
} else {
|
} else {
|
||||||
editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage()
|
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)
|
meta := existing[0].Metadata.(*waid.MessageMetadata)
|
||||||
if slices.Contains(meta.Edits, evt.Info.ID) {
|
if slices.Contains(meta.Edits, evt.Info.ID) {
|
||||||
return nil, fmt.Errorf("%w: edit already handled", bridgev2.ErrIgnoringRemoteEvent)
|
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.postHandle = func() {
|
||||||
evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false)
|
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])
|
editPart := cm.Parts[0].ToEditPart(existing[0])
|
||||||
if evt.isUndecryptableUpsertSubEvent || evt.dontRenderEdited {
|
if evt.isUndecryptableUpsertSubEvent {
|
||||||
if editPart.TopLevelExtra == nil {
|
if editPart.TopLevelExtra == nil {
|
||||||
editPart.TopLevelExtra = make(map[string]any)
|
editPart.TopLevelExtra = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,11 @@ var (
|
||||||
_ bridgev2.DeleteChatHandlingNetworkAPI = (*WhatsAppClient)(nil)
|
_ 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)
|
waMsg, optionMap, err := wa.Main.MsgConv.PollStartToWhatsApp(ctx, msg.Content, msg.ReplyTo, msg.Portal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert poll vote: %w", err)
|
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)
|
resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -68,21 +67,19 @@ func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridge
|
||||||
return resp, nil
|
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)
|
waMsg, err := wa.Main.MsgConv.PollVoteToWhatsApp(ctx, wa.Client, msg.Content, msg.VoteTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert poll vote: %w", err)
|
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)
|
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)
|
waMsg, req, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, msg.Event, msg.Content, msg.ReplyTo, msg.ThreadRoot, msg.Portal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert message: %w", err)
|
return nil, fmt.Errorf("failed to convert message: %w", err)
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req)
|
return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +154,7 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge
|
||||||
}, nil
|
}, 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)
|
messageID, err := waid.ParseMessageID(msg.TargetMessage.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse target message ID: %w", err)
|
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),
|
SenderTimestampMS: proto.Int64(msg.Event.Timestamp),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
var req whatsmeow.SendRequestExtra
|
var req whatsmeow.SendRequestExtra
|
||||||
if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup {
|
if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup {
|
||||||
reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage)
|
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
|
}, 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)
|
messageID, err := waid.ParseMessageID(msg.TargetReaction.MessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse target message ID: %w", err)
|
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)
|
extra.ID = types.MessageID(msg.InputTransactionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg, extra)
|
resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg, extra)
|
||||||
zerolog.Ctx(ctx).Trace().Any("response", resp).Msg("WhatsApp reaction response")
|
zerolog.Ctx(ctx).Trace().Any("response", resp).Msg("WhatsApp reaction response")
|
||||||
return err
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
var editID types.MessageID
|
var editID types.MessageID
|
||||||
|
|
@ -250,8 +245,6 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert message: %w", err)
|
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)
|
convertedEdit := wa.Client.BuildEdit(messageID.Chat, messageID.ID, waMsg)
|
||||||
if edit.OrigSender == nil {
|
if edit.OrigSender == nil {
|
||||||
convertedEdit.EditedMessage.Message.ProtocolMessage.TimestampMS = proto.Int64(edit.Event.Timestamp)
|
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
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
messageID, err := waid.ParseMessageID(msg.TargetMessage.ID)
|
messageID, err := waid.ParseMessageID(msg.TargetMessage.ID)
|
||||||
if err != nil {
|
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)
|
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)
|
revokeMessage := wa.Client.BuildRevoke(messageID.Chat, messageID.Sender, messageID.ID)
|
||||||
|
|
||||||
extra := whatsmeow.SendRequestExtra{}
|
extra := whatsmeow.SendRequestExtra{}
|
||||||
|
|
@ -291,7 +283,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br
|
||||||
return err
|
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) {
|
if !receipt.ReadUpTo.After(receipt.LastRead) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +322,6 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *
|
||||||
}
|
}
|
||||||
messagesToRead[key] = append(messagesToRead[key], parsed.ID)
|
messagesToRead[key] = append(messagesToRead[key], parsed.ID)
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(receipt, time.Now(), &retErr)
|
|
||||||
for messageSender, ids := range messagesToRead {
|
for messageSender, ids := range messagesToRead {
|
||||||
err = wa.Client.MarkRead(ctx, ids, receipt.Receipt.Timestamp, portalJID, messageSender)
|
err = wa.Client.MarkRead(ctx, ids, receipt.Receipt.Timestamp, portalJID, messageSender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -340,7 +331,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *
|
||||||
return err
|
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)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -361,7 +352,6 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
if wa.Main.Config.SendPresenceOnTyping {
|
if wa.Main.Config.SendPresenceOnTyping {
|
||||||
err = wa.updatePresence(ctx, types.PresenceAvailable)
|
err = wa.updatePresence(ctx, types.PresenceAvailable)
|
||||||
if err != nil {
|
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)
|
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)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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)
|
return false, fmt.Errorf("%w (%s)", errUnsupportedDisappearingTimer, msg.Content.Timer.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
settingTS := time.UnixMilli(msg.Event.Timestamp)
|
settingTS := time.UnixMilli(msg.Event.Timestamp)
|
||||||
err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS)
|
err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -402,7 +391,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg
|
||||||
return true, nil
|
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 {
|
if msg.Type.IsSelf && msg.OrigSender != nil {
|
||||||
return nil, 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)
|
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)
|
resp, err := wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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")
|
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)
|
err = wa.Client.SetGroupName(ctx, portalJID, msg.Content.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -484,7 +471,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev
|
||||||
return true, nil
|
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)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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")
|
return false, fmt.Errorf("cannot set room topic for DM")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
newID := wa.Client.GenerateMessageID()
|
newID := wa.Client.GenerateMessageID()
|
||||||
oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID
|
oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID
|
||||||
err = wa.Client.SetGroupTopic(ctx, portalJID, oldID, newID, msg.Content.Topic)
|
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
|
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)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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)
|
avatarID, err := wa.Client.SetGroupPhoto(ctx, portalJID, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -600,7 +585,7 @@ func convertRoomAvatar(data []byte) ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
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)
|
chatJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -611,16 +596,14 @@ func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMu
|
||||||
if !muted || mutedUntil == event.MutedForever {
|
if !muted || mutedUntil == event.MutedForever {
|
||||||
muteTS = nil
|
muteTS = nil
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
return wa.Client.SendAppState(ctx, appstate.BuildMuteAbs(chatJID, muted, muteTS))
|
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)
|
chatJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
_, isFavorite := msg.Content.Tags[event.RoomTagFavourite]
|
_, isFavorite := msg.Content.Tags[event.RoomTagFavourite]
|
||||||
return wa.Client.SendAppState(ctx, appstate.BuildPin(chatJID, isFavorite))
|
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
|
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)
|
chatJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -661,11 +644,10 @@ func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
return wa.Client.SendAppState(ctx, appstate.BuildMarkChatAsRead(chatJID, msg.Content.Unread, lastTS, lastKey))
|
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)
|
chatJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -686,6 +668,5 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
|
||||||
return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true))
|
return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/appstate"
|
"go.mau.fi/whatsmeow/appstate"
|
||||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
"go.mau.fi/whatsmeow/proto/waE2E"
|
||||||
|
"go.mau.fi/whatsmeow/store"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
"go.mau.fi/whatsmeow/types/events"
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
|
|
@ -76,7 +77,6 @@ func init() {
|
||||||
func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
log := wa.UserLogin.Log
|
log := wa.UserLogin.Log
|
||||||
ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx)
|
ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx)
|
||||||
wa.MC.OnWhatsAppEvent(rawEvt)
|
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
switch evt := rawEvt.(type) {
|
switch evt := rawEvt.(type) {
|
||||||
|
|
@ -126,20 +126,6 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
success = wa.handleWANewsletterLeave(evt)
|
success = wa.handleWANewsletterLeave(evt)
|
||||||
case *events.Picture:
|
case *events.Picture:
|
||||||
success = wa.handleWAPictureUpdate(ctx, evt)
|
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:
|
case *events.AppStateSyncComplete:
|
||||||
wa.handleWAAppStateSyncComplete(ctx, evt)
|
wa.handleWAAppStateSyncComplete(ctx, evt)
|
||||||
|
|
@ -182,6 +168,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
}()
|
}()
|
||||||
go wa.syncRemoteProfile(ctx, nil)
|
go wa.syncRemoteProfile(ctx, nil)
|
||||||
}
|
}
|
||||||
|
wa.MC.OnConnect(store.GetWAVersion()[2], wa.Device.Platform)
|
||||||
case *events.OfflineSyncPreview:
|
case *events.OfflineSyncPreview:
|
||||||
log.Info().
|
log.Info().
|
||||||
Int("message_count", evt.Messages).
|
Int("message_count", evt.Messages).
|
||||||
|
|
@ -372,32 +359,24 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dontRenderEdited := false
|
|
||||||
messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation()
|
messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation()
|
||||||
if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD {
|
if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD {
|
||||||
parentKey := messageAssoc.GetParentMessageKey()
|
parentKey := messageAssoc.GetParentMessageKey()
|
||||||
protocolMsg := evt.Message.GetProtocolMessage()
|
associatedMessage := evt.Message.GetAssociatedChildMessage().GetMessage()
|
||||||
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
|
|
||||||
}
|
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Str("message_id", evt.Info.ID).
|
Str("message_id", evt.Info.ID).
|
||||||
Str("parent_id", parentKey.GetID()).
|
Str("parent_id", parentKey.GetID()).
|
||||||
Stringer("assoc_type", assocType).
|
Stringer("assoc_type", assocType).
|
||||||
Msg("Received HD replacement message, converting to edit")
|
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{
|
evt.Message = &waE2E.Message{
|
||||||
ProtocolMessage: protocolMsg,
|
ProtocolMessage: protocolMsg,
|
||||||
}
|
}
|
||||||
parsedMessageType = getMessageType(evt.Message)
|
|
||||||
} else if assocType == waE2E.MessageAssociation_MOTION_PHOTO {
|
} else if assocType == waE2E.MessageAssociation_MOTION_PHOTO {
|
||||||
//evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage()
|
//evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage()
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
|
|
@ -416,7 +395,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
MsgEvent: evt,
|
MsgEvent: evt,
|
||||||
|
|
||||||
parsedMessageType: parsedMessageType,
|
parsedMessageType: parsedMessageType,
|
||||||
dontRenderEdited: dontRenderEdited,
|
|
||||||
})
|
})
|
||||||
return res.Success
|
return res.Success
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -13,7 +12,6 @@ import (
|
||||||
"go.mau.fi/util/exsync"
|
"go.mau.fi/util/exsync"
|
||||||
"go.mau.fi/util/jsontime"
|
"go.mau.fi/util/jsontime"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/types"
|
|
||||||
"go.mau.fi/whatsmeow/types/events"
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
waLog "go.mau.fi/whatsmeow/util/log"
|
waLog "go.mau.fi/whatsmeow/util/log"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
|
|
@ -27,7 +25,6 @@ const (
|
||||||
LoginStepIDQR = "fi.mau.whatsapp.login.qr"
|
LoginStepIDQR = "fi.mau.whatsapp.login.qr"
|
||||||
LoginStepIDPhoneNumber = "fi.mau.whatsapp.login.phone"
|
LoginStepIDPhoneNumber = "fi.mau.whatsapp.login.phone"
|
||||||
LoginStepIDCode = "fi.mau.whatsapp.login.code"
|
LoginStepIDCode = "fi.mau.whatsapp.login.code"
|
||||||
LoginStepIDPasskey = "fi.mau.whatsapp.login.passkey"
|
|
||||||
LoginStepIDComplete = "fi.mau.whatsapp.login.complete"
|
LoginStepIDComplete = "fi.mau.whatsapp.login.complete"
|
||||||
|
|
||||||
LoginFlowIDQR = "qr"
|
LoginFlowIDQR = "qr"
|
||||||
|
|
@ -79,8 +76,8 @@ var (
|
||||||
}
|
}
|
||||||
ErrRateLimitedByWhatsApp = bridgev2.RespError{
|
ErrRateLimitedByWhatsApp = bridgev2.RespError{
|
||||||
ErrCode: "FI.MAU.WHATSAPP.RATE_LIMITED",
|
ErrCode: "FI.MAU.WHATSAPP.RATE_LIMITED",
|
||||||
Err: "Rate limited by WhatsApp. Try again later.",
|
Err: "Rate limited by WhatsApp",
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusTooManyRequests,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -96,8 +93,6 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User,
|
||||||
|
|
||||||
WaitForQRs: exsync.NewEvent(),
|
WaitForQRs: exsync.NewEvent(),
|
||||||
LoginComplete: exsync.NewEvent(),
|
LoginComplete: exsync.NewEvent(),
|
||||||
PasskeyRequest: exsync.NewEvent(),
|
|
||||||
PasskeyConfirmation: exsync.NewEvent(),
|
|
||||||
Received515: exsync.NewEvent(),
|
Received515: exsync.NewEvent(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -119,11 +114,6 @@ type WALogin struct {
|
||||||
Received515 *exsync.Event
|
Received515 *exsync.Event
|
||||||
PrevQRIndex atomic.Int32
|
PrevQRIndex atomic.Int32
|
||||||
|
|
||||||
PasskeyRequest *exsync.Event
|
|
||||||
PasskeyRequestData *events.PairPasskeyRequest
|
|
||||||
PasskeyConfirmation *exsync.Event
|
|
||||||
PasskeyConfirmationData *events.PairPasskeyConfirmation
|
|
||||||
|
|
||||||
Closed atomic.Bool
|
Closed atomic.Bool
|
||||||
EventHandlerID uint32
|
EventHandlerID uint32
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +122,6 @@ var (
|
||||||
_ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil)
|
_ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil)
|
||||||
_ bridgev2.LoginProcessUserInput = (*WALogin)(nil)
|
_ bridgev2.LoginProcessUserInput = (*WALogin)(nil)
|
||||||
_ bridgev2.LoginProcessWithOverride = (*WALogin)(nil)
|
_ bridgev2.LoginProcessWithOverride = (*WALogin)(nil)
|
||||||
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const LoginConnectWait = 15 * time.Second
|
const LoginConnectWait = 15 * time.Second
|
||||||
|
|
@ -273,17 +262,6 @@ func (wl *WALogin) handleEvent(rawEvt any) {
|
||||||
case *events.ClientOutdated:
|
case *events.ClientOutdated:
|
||||||
wl.Log.Error().Msg("Got client outdated error")
|
wl.Log.Error().Msg("Got client outdated error")
|
||||||
wl.LoginError = ErrLoginClientOutdated
|
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:
|
case *events.PairSuccess:
|
||||||
wl.Log.Info().Any("event_data", evt).Msg("Got pair successful event")
|
wl.Log.Info().Any("event_data", evt).Msg("Got pair successful event")
|
||||||
wl.LoginSuccess = evt
|
wl.LoginSuccess = evt
|
||||||
|
|
@ -311,14 +289,10 @@ func (wl *WALogin) handleEvent(rawEvt any) {
|
||||||
|
|
||||||
func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
if wl.PhoneCode {
|
if wl.PhoneCode {
|
||||||
select {
|
err := wl.LoginComplete.Wait(ctx)
|
||||||
case <-ctx.Done():
|
if err != nil {
|
||||||
wl.Cancel()
|
wl.Cancel()
|
||||||
return nil, ctx.Err()
|
return nil, err
|
||||||
case <-wl.PasskeyRequest.GetChan():
|
|
||||||
return wl.makePasskeyStep()
|
|
||||||
case <-wl.LoginComplete.GetChan():
|
|
||||||
// continue
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prevIndex := int(wl.PrevQRIndex.Load())
|
prevIndex := int(wl.PrevQRIndex.Load())
|
||||||
|
|
@ -345,16 +319,9 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
wl.Cancel()
|
wl.Cancel()
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
case <-wl.PasskeyRequest.GetChan():
|
|
||||||
return wl.makePasskeyStep()
|
|
||||||
case <-wl.LoginComplete.GetChan():
|
case <-wl.LoginComplete.GetChan():
|
||||||
// continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wl.onLoginComplete(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, error) {
|
|
||||||
if wl.LoginError != nil {
|
if wl.LoginError != nil {
|
||||||
wl.Log.Debug().Err(wl.LoginError).Msg("Login completed with error")
|
wl.Log.Debug().Err(wl.LoginError).Msg("Login completed with error")
|
||||||
wl.Cancel()
|
wl.Cancel()
|
||||||
|
|
@ -405,66 +372,6 @@ func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, er
|
||||||
}, nil
|
}, 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() {
|
func (wl *WALogin) Cancel() {
|
||||||
wl.Closed.Store(true)
|
wl.Closed.Store(true)
|
||||||
wl.Client.RemoveEventHandler(wl.EventHandlerID)
|
wl.Client.RemoveEventHandler(wl.EventHandlerID)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.mau.fi/util/ptr"
|
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
waBinary "go.mau.fi/whatsmeow/binary"
|
waBinary "go.mau.fi/whatsmeow/binary"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
|
@ -40,16 +39,14 @@ func (wa *WhatsAppClient) initMC() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type mClient = interface {
|
type mClient = interface {
|
||||||
OnMatrixEvent(any, time.Duration, error)
|
OnConnect(version uint32, platform string)
|
||||||
OnWhatsAppEvent(any)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type noopMC struct{}
|
type noopMC struct{}
|
||||||
|
|
||||||
var noopMCInstance mClient = &noopMC{}
|
var noopMCInstance mClient = &noopMC{}
|
||||||
|
|
||||||
func (n *noopMC) OnMatrixEvent(any, time.Duration, error) {}
|
func (n *noopMC) OnConnect(version uint32, platform string) {}
|
||||||
func (n *noopMC) OnWhatsAppEvent(any) {}
|
|
||||||
|
|
||||||
type mWAClient = interface {
|
type mWAClient = interface {
|
||||||
MSend(data []byte)
|
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")
|
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))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,10 @@ func (wa *WhatsAppClient) processFailedMedia(ctx context.Context, portalKey netw
|
||||||
func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) {
|
func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) {
|
||||||
log := wa.UserLogin.Log.With().Str("loop", "media requests").Logger()
|
log := wa.UserLogin.Log.With().Str("loop", "media requests").Logger()
|
||||||
ctx = log.WithContext(ctx)
|
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
|
var startIn time.Duration
|
||||||
if err == nil && userTz != nil {
|
if tzName != "" && err == nil && userTz != nil {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
startAt := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, userTz)
|
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)
|
startAt = startAt.Add(time.Duration(wa.Main.Config.HistorySync.MediaRequests.RequestLocalTime) * time.Minute)
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,6 @@ func (wa *WhatsAppClient) FillBridgeState(state status.BridgeState) status.Bridg
|
||||||
state.Error = WAPhoneOffline
|
state.Error = WAPhoneOffline
|
||||||
state.UserAction = status.UserActionOpenNative
|
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
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ var ResyncLoopInterval = 4 * time.Hour
|
||||||
var ResyncJitterSeconds = 3600
|
var ResyncJitterSeconds = 3600
|
||||||
|
|
||||||
func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) {
|
func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) {
|
||||||
lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time
|
if ghost.Metadata.(*waid.GhostMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) {
|
||||||
if lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wa.resyncQueueLock.Lock()
|
wa.resyncQueueLock.Lock()
|
||||||
|
|
@ -44,7 +43,6 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) {
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Stringer("jid", jid).
|
Stringer("jid", jid).
|
||||||
Str("next_resync_in", nextResyncIn).
|
Str("next_resync_in", nextResyncIn).
|
||||||
Time("last_ghost_resync", lastSync).
|
|
||||||
Msg("Enqueued resync for ghost")
|
Msg("Enqueued resync for ghost")
|
||||||
}
|
}
|
||||||
wa.resyncQueueLock.Unlock()
|
wa.resyncQueueLock.Unlock()
|
||||||
|
|
@ -52,8 +50,7 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) {
|
||||||
|
|
||||||
func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) {
|
func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) {
|
||||||
jid, _ := waid.ParsePortalID(portal.ID)
|
jid, _ := waid.ParsePortalID(portal.ID)
|
||||||
lastSync := portal.Metadata.(*waid.PortalMetadata).LastSync.Time
|
if portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) {
|
||||||
if lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
|
||||||
return
|
return
|
||||||
} else if !allowDM && jid.Server != types.GroupServer {
|
} else if !allowDM && jid.Server != types.GroupServer {
|
||||||
return
|
return
|
||||||
|
|
@ -64,7 +61,6 @@ func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM b
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Stringer("jid", jid).
|
Stringer("jid", jid).
|
||||||
Stringer("next_resync_in", time.Until(wa.nextResync)).
|
Stringer("next_resync_in", time.Until(wa.nextResync)).
|
||||||
Time("last_portal_resync", lastSync).
|
|
||||||
Msg("Enqueued resync for portal")
|
Msg("Enqueued resync for portal")
|
||||||
}
|
}
|
||||||
wa.resyncQueueLock.Unlock()
|
wa.resyncQueueLock.Unlock()
|
||||||
|
|
@ -361,7 +357,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2.
|
||||||
wrappedAvatar = &bridgev2.Avatar{
|
wrappedAvatar = &bridgev2.Avatar{
|
||||||
ID: networkid.AvatarID(avatar.ID),
|
ID: networkid.AvatarID(avatar.ID),
|
||||||
Get: func(ctx context.Context) ([]byte, error) {
|
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, "", "")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ func getMessageType(waMsg *waE2E.Message) string {
|
||||||
return "secret encrypted"
|
return "secret encrypted"
|
||||||
case waMsg.PollResultSnapshotMessage != nil:
|
case waMsg.PollResultSnapshotMessage != nil:
|
||||||
return "poll result snapshot"
|
return "poll result snapshot"
|
||||||
case waMsg.MessageHistoryNotice != nil:
|
case waMsg.MessageHistoryBundle != nil:
|
||||||
return "message history notice"
|
return "message history bundle"
|
||||||
case waMsg.RequestPhoneNumberMessage != nil:
|
case waMsg.RequestPhoneNumberMessage != nil:
|
||||||
return "request phone number"
|
return "request phone number"
|
||||||
case waMsg.KeepInChatMessage != nil:
|
case waMsg.KeepInChatMessage != nil:
|
||||||
|
|
@ -152,7 +152,7 @@ func getMessageType(waMsg *waE2E.Message) string {
|
||||||
return "chat"
|
return "chat"
|
||||||
case waMsg.PlaceholderMessage != nil:
|
case waMsg.PlaceholderMessage != nil:
|
||||||
return "placeholder"
|
return "placeholder"
|
||||||
case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil, waMsg.MessageHistoryBundle != nil:
|
case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil:
|
||||||
return "ignore"
|
return "ignore"
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,6 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
part, contextInfo = mc.convertPlaceholderMessage(ctx, waMsg)
|
part, contextInfo = mc.convertPlaceholderMessage(ctx, waMsg)
|
||||||
case waMsg.GroupInviteMessage != nil:
|
case waMsg.GroupInviteMessage != nil:
|
||||||
part, contextInfo = mc.convertGroupInviteMessage(ctx, info, waMsg.GroupInviteMessage)
|
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:
|
case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waE2E.ProtocolMessage_EPHEMERAL_SETTING:
|
||||||
part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill)
|
part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill)
|
||||||
case waMsg.EncCommentMessage != nil:
|
case waMsg.EncCommentMessage != nil:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"image"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -52,7 +51,7 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE
|
||||||
MsgType: event.MsgLocation,
|
MsgType: event.MsgLocation,
|
||||||
Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url),
|
Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url),
|
||||||
Format: event.FormatHTML,
|
Format: event.FormatHTML,
|
||||||
FormattedBody: fmt.Sprintf(`Location: <a href="%s">%s</a><br>%s`, html.EscapeString(url), html.EscapeString(name), html.EscapeString(msg.GetAddress())),
|
FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a><br>%s", url, name, msg.GetAddress()),
|
||||||
GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()),
|
GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exfmt"
|
|
||||||
"go.mau.fi/util/exmime"
|
"go.mau.fi/util/exmime"
|
||||||
"go.mau.fi/util/exslices"
|
"go.mau.fi/util/exslices"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
|
|
@ -91,7 +90,7 @@ func (mc *MessageConverter) convertMediaMessage(
|
||||||
var err error
|
var err error
|
||||||
portal := getPortal(ctx)
|
portal := getPortal(ctx)
|
||||||
idOverride := getEditTargetID(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 {
|
if err != nil {
|
||||||
panic(fmt.Errorf("failed to generate content URI: %w", err))
|
panic(fmt.Errorf("failed to generate content URI: %w", err))
|
||||||
}
|
}
|
||||||
|
|
@ -120,20 +119,13 @@ func (mc *MessageConverter) convertMediaMessage(
|
||||||
return
|
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) {
|
func (mc *MessageConverter) convertAlbumMessage(ctx context.Context, msg *waE2E.AlbumMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) {
|
||||||
parts := make([]string, 0, 2)
|
parts := make([]string, 0, 2)
|
||||||
if msg.GetExpectedImageCount() > 0 {
|
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 {
|
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
|
var partDesc string
|
||||||
if len(parts) > 0 {
|
if len(parts) > 0 {
|
||||||
|
|
@ -375,7 +367,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment(
|
||||||
var err error
|
var err error
|
||||||
part.URL, part.File, err = intent.UploadMediaStream(ctx, roomID, -1, true, func(file io.Writer) (*bridgev2.FileStreamResult, 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))
|
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")
|
zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err)
|
return nil, fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err)
|
||||||
|
|
@ -396,7 +388,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data, err := client.Download(ctx, message)
|
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")
|
zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err)
|
return fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err)
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exerrors"
|
"go.mau.fi/util/exerrors"
|
||||||
"go.mau.fi/util/exfmt"
|
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/whatsmeow/proto/waAICommonDeprecated"
|
"go.mau.fi/whatsmeow/proto/waAICommonDeprecated"
|
||||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
"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{
|
content := &event.MessageEventContent{
|
||||||
MsgType: event.MsgText,
|
MsgType: event.MsgText,
|
||||||
Body: format.HTMLToText(htmlMessage),
|
Body: format.HTMLToText(htmlMessage),
|
||||||
|
|
@ -119,92 +117,6 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info
|
||||||
}, msg.GetContextInfo()
|
}, 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) {
|
func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage, ts time.Time, isBackfill bool) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) {
|
||||||
portal := getPortal(ctx)
|
portal := getPortal(ctx)
|
||||||
portalMeta := portal.Metadata.(*waid.PortalMetadata)
|
portalMeta := portal.Metadata.(*waid.PortalMetadata)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ type UserLoginMetadata struct {
|
||||||
AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"`
|
AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"`
|
||||||
|
|
||||||
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"`
|
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"`
|
||||||
ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"`
|
|
||||||
|
|
||||||
MData json.RawMessage `json:"mdata,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
|
type MessageErrorType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ type ParsedMessageID struct {
|
||||||
Chat types.JID
|
Chat types.JID
|
||||||
Sender types.JID
|
Sender types.JID
|
||||||
ID types.MessageID
|
ID types.MessageID
|
||||||
Version []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pmi *ParsedMessageID) String() networkid.MessageID {
|
func (pmi *ParsedMessageID) String() networkid.MessageID {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ const (
|
||||||
mediaIDTypeStickerPackItem = 252
|
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())
|
compactChat := compactJID(messageInfo.Chat.ToNonAD())
|
||||||
compactSender := compactJID(messageInfo.Sender.ToNonAD())
|
compactSender := compactJID(messageInfo.Sender.ToNonAD())
|
||||||
receiverID := compactJID(ParseUserLoginID(receiver, 0))
|
receiverID := compactJID(ParseUserLoginID(receiver, 0))
|
||||||
|
|
@ -46,7 +46,7 @@ func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, rec
|
||||||
} else {
|
} else {
|
||||||
compactID = compactMsgID(messageInfo.ID)
|
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, mediaIDTypeMessage)
|
||||||
mediaID = append(mediaID, byte(len(compactChat)))
|
mediaID = append(mediaID, byte(len(compactChat)))
|
||||||
mediaID = append(mediaID, 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, receiverID...)
|
||||||
mediaID = append(mediaID, byte(len(compactID)))
|
mediaID = append(mediaID, byte(len(compactID)))
|
||||||
mediaID = append(mediaID, compactID...)
|
mediaID = append(mediaID, compactID...)
|
||||||
mediaID = append(mediaID, byte(len(version)))
|
|
||||||
mediaID = append(mediaID, version...)
|
|
||||||
return mediaID
|
return mediaID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,12 +138,6 @@ func ParseMediaID(mediaID networkid.MediaID) (*ParsedMediaID, error) {
|
||||||
Sender: senderJID,
|
Sender: senderJID,
|
||||||
ID: id,
|
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)
|
parsed.UserLogin = MakeUserLoginID(receiverID)
|
||||||
case mediaIDTypeAvatar, mediaIDTypeCommunityAvatar:
|
case mediaIDTypeAvatar, mediaIDTypeCommunityAvatar:
|
||||||
targetJID, err := readCompact(&mediaID, parseCompactJID)
|
targetJID, err := readCompact(&mediaID, parseCompactJID)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue