mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-08-22 12:04:55 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70eb6b135 | ||
|
|
bbc9a37b30 | ||
|
|
ba58807b0f |
||
|
|
5ef4c47b44 | ||
|
|
0f1832595f |
6 changed files with 60 additions and 51 deletions
8
.github/workflows/go.yml
vendored
8
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.25", "1.26"]
|
go-version: ["1.26", "1.27"]
|
||||||
name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
|
||||||
10
go.mod
10
go.mod
|
|
@ -1,8 +1,8 @@
|
||||||
module go.mau.fi/mautrix-whatsapp
|
module go.mau.fi/mautrix-whatsapp
|
||||||
|
|
||||||
go 1.25.0
|
go 1.26.0
|
||||||
|
|
||||||
toolchain go1.26.6
|
toolchain go1.27.0
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
tool go.mau.fi/util/cmd/maubuild
|
||||||
|
|
||||||
|
|
@ -10,15 +10,15 @@ 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.10.0
|
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde
|
||||||
go.mau.fi/webp v0.3.0
|
go.mau.fi/webp v0.3.0
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837
|
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629
|
||||||
golang.org/x/image v0.45.0
|
golang.org/x/image v0.45.0
|
||||||
golang.org/x/net v0.58.0
|
golang.org/x/net v0.58.0
|
||||||
golang.org/x/sync v0.22.0
|
golang.org/x/sync v0.22.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.12
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mautrix v0.30.0
|
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
||||||
12
go.sum
12
go.sum
|
|
@ -71,12 +71,12 @@ github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU=
|
go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU=
|
||||||
go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0=
|
go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0=
|
||||||
go.mau.fi/util v0.10.0 h1:vH9IXZmfBKa96p47HxrVqEPkrj02zDJg3o4EF172+Lk=
|
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbsxj7G6mfujjKei1HaFQM=
|
||||||
go.mau.fi/util v0.10.0/go.mod h1:uZwpm9sK4wO2Qqy+t6QoVq29szMsRxWXp9/BkQLG4xk=
|
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8=
|
||||||
go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
||||||
go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo=
|
go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837 h1:xJ13dqFcK/oPNImltlk7sumI/uiQ9EON/6g/iwpT1Nc=
|
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837/go.mod h1:UFP0D7aj+biuFejsllcoNiHTQorh0ODFUyG4Txpqfks=
|
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg=
|
||||||
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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
|
|
@ -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.30.0 h1:bad+q7w5tLqiHpr+oUxVI+8m8ePbV3AvoFKg2jQzPyo=
|
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA=
|
||||||
maunium.net/go/mautrix v0.30.0/go.mod h1:bb0gjxbTFOqTaAYKGw5E7j9XROUR2Sl1Etm3IbmYXbo=
|
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0=
|
||||||
|
|
|
||||||
|
|
@ -247,12 +247,14 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
return c.Stringer("chat_jid", jid)
|
return c.Stringer("chat_jid", jid)
|
||||||
})
|
})
|
||||||
|
|
||||||
var minTime, maxTime, firstItemTime, lastItemTime time.Time
|
var firstItemTime, lastItemTime time.Time
|
||||||
var minTimeIndex, maxTimeIndex int
|
|
||||||
|
|
||||||
ignoredTypes := 0
|
ignoredTypes := 0
|
||||||
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(conv.GetMessages()))
|
rawMessages := conv.GetMessages()
|
||||||
for i, rawMsg := range conv.GetMessages() {
|
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(rawMessages))
|
||||||
|
allowClamp := conv.GetCommentsCount() == 0
|
||||||
|
var newerTS uint64
|
||||||
|
for i, rawMsg := range rawMessages {
|
||||||
// Don't store messages that will just be skipped.
|
// Don't store messages that will just be skipped.
|
||||||
msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage())
|
msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -270,20 +272,30 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
firstItemTime = msgEvt.Info.Timestamp
|
firstItemTime = msgEvt.Info.Timestamp
|
||||||
}
|
}
|
||||||
lastItemTime = msgEvt.Info.Timestamp
|
lastItemTime = msgEvt.Info.Timestamp
|
||||||
if minTime.IsZero() || msgEvt.Info.Timestamp.Before(minTime) {
|
|
||||||
minTime = msgEvt.Info.Timestamp
|
|
||||||
minTimeIndex = i
|
|
||||||
}
|
|
||||||
if maxTime.IsZero() || msgEvt.Info.Timestamp.After(maxTime) {
|
|
||||||
maxTime = msgEvt.Info.Timestamp
|
|
||||||
maxTimeIndex = i
|
|
||||||
}
|
|
||||||
|
|
||||||
msgType := getMessageType(msgEvt.Message)
|
msgType := getMessageType(msgEvt.Message)
|
||||||
if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") {
|
if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") {
|
||||||
ignoredTypes++
|
ignoredTypes++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Comments (replies) in announcement groups are not ordered by timestamp, so don't clamp them.
|
||||||
|
if rawMsg.GetMessage().GetCommentMetadata().GetCommentParentKey() != nil {
|
||||||
|
allowClamp = false
|
||||||
|
}
|
||||||
|
// WhatsApp has bugs where some random messages will have timestamps decades in the future.
|
||||||
|
// To ensure they don't mess up our ordering, require timestamps of older messages to be
|
||||||
|
// before the previous (newer) message.
|
||||||
|
if currentTS := rawMsg.GetMessage().GetMessageTimestamp(); newerTS > 0 && allowClamp && currentTS > newerTS {
|
||||||
|
log.Warn().
|
||||||
|
Time("current_ts", time.Unix(int64(currentTS), 0)).
|
||||||
|
Time("prev_ts", time.Unix(int64(newerTS), 0)).
|
||||||
|
Int("msg_index", i).
|
||||||
|
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
||||||
|
Msg("Clamping message timestamp")
|
||||||
|
rawMsg.Message.MessageTimestamp = ptr.Ptr(newerTS)
|
||||||
|
msgEvt.Info.Timestamp = time.Unix(int64(newerTS), 0)
|
||||||
|
}
|
||||||
|
newerTS = rawMsg.GetMessage().GetMessageTimestamp()
|
||||||
marshaled, err := proto.Marshal(rawMsg)
|
marshaled, err := proto.Marshal(rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).
|
log.Warn().Err(err).
|
||||||
|
|
@ -298,13 +310,8 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Int("wrapped_count", len(messages)).
|
Int("wrapped_count", len(messages)).
|
||||||
Int("ignored_msg_type_count", ignoredTypes).
|
Int("ignored_msg_type_count", ignoredTypes).
|
||||||
Time("lowest_time", minTime).
|
|
||||||
Int("lowest_time_index", minTimeIndex).
|
|
||||||
Time("highest_time", maxTime).
|
|
||||||
Int("highest_time_index", maxTimeIndex).
|
|
||||||
Time("first_item_time", firstItemTime).
|
Time("first_item_time", firstItemTime).
|
||||||
Time("last_item_time", lastItemTime).
|
Time("last_item_time", lastItemTime).
|
||||||
Bool("highest_time_mismatch", firstItemTime != maxTime).
|
|
||||||
Dict("metadata", zerolog.Dict().
|
Dict("metadata", zerolog.Dict().
|
||||||
Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()).
|
Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()).
|
||||||
Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()).
|
Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()).
|
||||||
|
|
@ -320,7 +327,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
Msg("Collected messages to save from history sync conversation")
|
Msg("Collected messages to save from history sync conversation")
|
||||||
|
|
||||||
if len(messages) > 0 {
|
if len(messages) > 0 {
|
||||||
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime))
|
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, firstItemTime))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if stopOnError {
|
if stopOnError {
|
||||||
return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err)
|
return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err)
|
||||||
|
|
|
||||||
|
|
@ -284,14 +284,13 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
Ban: ptr.Ptr(nobodyPL),
|
Ban: ptr.Ptr(nobodyPL),
|
||||||
// TODO allow invites if bridge config says to allow them, or maybe if relay mode is enabled?
|
// TODO allow invites if bridge config says to allow them, or maybe if relay mode is enabled?
|
||||||
Events: map[event.Type]int{
|
Events: map[event.Type]int{
|
||||||
event.StateRoomName: metaChangePL,
|
event.StateRoomName: metaChangePL,
|
||||||
event.StateRoomAvatar: metaChangePL,
|
event.StateRoomAvatar: metaChangePL,
|
||||||
event.StateTopic: metaChangePL,
|
event.StateTopic: metaChangePL,
|
||||||
event.EventReaction: defaultPL,
|
event.EventReaction: defaultPL,
|
||||||
event.EventRedaction: defaultPL,
|
event.EventRedaction: defaultPL,
|
||||||
|
event.EventUnstablePollResponse: defaultPL,
|
||||||
event.StateBeeperDisappearingTimer: metaChangePL,
|
event.StateBeeperDisappearingTimer: metaChangePL,
|
||||||
// TODO always allow poll responses
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -551,12 +550,12 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne
|
||||||
StateDefault: ptr.Ptr(nobodyPL),
|
StateDefault: ptr.Ptr(nobodyPL),
|
||||||
Ban: ptr.Ptr(nobodyPL),
|
Ban: ptr.Ptr(nobodyPL),
|
||||||
Events: map[event.Type]int{
|
Events: map[event.Type]int{
|
||||||
event.StateRoomName: adminPL,
|
event.StateRoomName: adminPL,
|
||||||
event.StateRoomAvatar: adminPL,
|
event.StateRoomAvatar: adminPL,
|
||||||
event.StateTopic: adminPL,
|
event.StateTopic: adminPL,
|
||||||
event.EventReaction: defaultPL,
|
event.EventReaction: defaultPL,
|
||||||
event.EventRedaction: defaultPL,
|
event.EventRedaction: defaultPL,
|
||||||
// TODO always allow poll responses
|
event.EventUnstablePollResponse: defaultPL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,8 @@ var (
|
||||||
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
const LoginConnectWait = 15 * time.Second
|
const LoginConnectWait = 30 * time.Second
|
||||||
|
const LoginPairPhoneWait = 30 * time.Second
|
||||||
|
|
||||||
func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect)
|
wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect)
|
||||||
|
|
@ -193,19 +194,21 @@ func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
|
func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, LoginConnectWait)
|
|
||||||
defer cancel()
|
|
||||||
err := wl.Client.Connect()
|
err := wl.Client.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login")
|
wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = wl.WaitForQRs.Wait(ctx)
|
connectCtx, cancelConnect := context.WithTimeout(ctx, LoginConnectWait)
|
||||||
|
err = wl.WaitForQRs.Wait(connectCtx)
|
||||||
|
cancelConnect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Warn().Err(err).Msg("Timed out waiting for connection")
|
wl.Log.Warn().Err(err).Msg("Timed out waiting for connection")
|
||||||
return nil, fmt.Errorf("failed to wait for connection: %w", err)
|
return nil, fmt.Errorf("failed to wait for connection: %w", err)
|
||||||
}
|
}
|
||||||
pairingCode, err := wl.Client.PairPhone(ctx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
pairCtx, cancelPair := context.WithTimeout(ctx, LoginPairPhoneWait)
|
||||||
|
defer cancelPair()
|
||||||
|
pairingCode, err := wl.Client.PairPhone(pairCtx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Err(err).Msg("Failed to request phone code login")
|
wl.Log.Err(err).Msg("Failed to request phone code login")
|
||||||
if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) {
|
if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue