analytics: track undecryptable messages

This commit is contained in:
Tulir Asokan 2024-09-27 14:44:15 +03:00
parent 512f450847
commit 5deafa0112
6 changed files with 129 additions and 3 deletions

2
go.mod
View file

@ -22,7 +22,7 @@ require (
golang.org/x/sync v0.8.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.21.1-0.20240926091654-7a5f15b03c2e
maunium.net/go/mautrix v0.21.1-0.20240927113633-d1e5b09d972b
)
require (

4
go.sum
View file

@ -114,5 +114,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/mautrix v0.21.1-0.20240926091654-7a5f15b03c2e h1:elQExbBoPGaI0jKMt2IzhpGT7NoTUqZCFxNSvRw0s1A=
maunium.net/go/mautrix v0.21.1-0.20240926091654-7a5f15b03c2e/go.mod h1:qm9oDhcHxF/Xby5RUuONIGpXw1SXXqLZj/GgvMxJxu0=
maunium.net/go/mautrix v0.21.1-0.20240927113633-d1e5b09d972b h1:YeiQzkT0HXTqcH7BgbsNiD5CbUpEf80pf/wUpCTQ3j4=
maunium.net/go/mautrix v0.21.1-0.20240927113633-d1e5b09d972b/go.mod h1:qm9oDhcHxF/Xby5RUuONIGpXw1SXXqLZj/GgvMxJxu0=

View file

@ -0,0 +1,74 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"fmt"
"go.mau.fi/whatsmeow/proto/waE2E"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
)
func (wa *WhatsAppClient) obfuscateJID(jid types.JID) string {
// Turn the first 4 bytes of HMAC-SHA256(user_mxid, phone) into a number and replace the middle of the actual phone with that deterministic random number.
randomNumber := binary.BigEndian.Uint32(hmac.New(sha256.New, []byte(wa.UserLogin.UserMXID)).Sum([]byte(jid.User))[:4])
return fmt.Sprintf("+%s-%d-%s:%d", jid.User[:1], randomNumber, jid.User[len(jid.User)-2:], jid.Device)
}
func (wa *WhatsAppClient) trackUndecryptable(evt *events.UndecryptableMessage) {
metricType := "error"
if evt.IsUnavailable {
metricType = "unavailable"
}
wa.UserLogin.TrackAnalytics("WhatsApp undecryptable message", map[string]any{
"messageID": evt.Info.ID,
"undecryptableType": metricType,
"decryptFailMode": evt.DecryptFailMode,
})
}
func (wa *WhatsAppClient) trackUndecryptableResolved(evt *events.Message) {
resolveType := "sender"
if evt.UnavailableRequestID != "" {
resolveType = "phone"
}
wa.UserLogin.TrackAnalytics("WhatsApp undecryptable message resolved", map[string]any{
"messageID": evt.Info.ID,
"resolveType": resolveType,
})
}
func (wa *WhatsAppClient) trackFoundRetry(receipt *events.Receipt, messageID types.MessageID, retryCount int, msg *waE2E.Message) bool {
wa.UserLogin.TrackAnalytics("WhatsApp incoming retry (accepted)", map[string]any{
"requester": wa.obfuscateJID(receipt.Sender),
"messageID": messageID,
"retryCount": retryCount,
})
return true
}
func (wa *WhatsAppClient) trackNotFoundRetry(requester, to types.JID, id types.MessageID) *waE2E.Message {
wa.UserLogin.TrackAnalytics("WhatsApp incoming retry (message not found)", map[string]any{
"requester": wa.obfuscateJID(requester),
"messageID": id,
})
return nil
}

View file

@ -1,3 +1,19 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
@ -47,6 +63,8 @@ func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.Us
w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log))
w.Client.AddEventHandler(w.handleWAEvent)
w.Client.AutomaticMessageRerequestFromPhone = true
w.Client.GetMessageForRetry = w.trackNotFoundRetry
w.Client.PreRetryCallback = w.trackFoundRetry
w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts)
} else {
w.UserLogin.Log.Warn().Stringer("jid", w.JID).Msg("No device found for user in whatsmeow store")

View file

@ -1,3 +1,19 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
@ -169,6 +185,7 @@ func (evt *WAMessageEvent) GetType() bridgev2.RemoteEventType {
func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, existing []*database.Message) (bridgev2.UpsertResult, error) {
if existing[0].Metadata.(*waid.MessageMetadata).Error == waid.MsgErrDecryptionFailed {
evt.wa.trackUndecryptableResolved(evt.MsgEvent)
zerolog.Ctx(ctx).Debug().
Stringer("existing_mxid", existing[0].MXID).
Msg("Received decryptable version of previously undecryptable message")

View file

@ -1,3 +1,19 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
@ -242,6 +258,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable
Bool("unavailable", evt.IsUnavailable).
Str("decrypt_fail", string(evt.DecryptFailMode)).
Msg("Received undecryptable WhatsApp message")
wa.trackUndecryptable(evt)
if evt.DecryptFailMode == events.DecryptFailHide || evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer {
return
}