mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-11-09 23:59:17 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fd355360c |
28 changed files with 121 additions and 628 deletions
1
.github/workflows/go.yml
vendored
1
.github/workflows/go.yml
vendored
|
|
@ -30,7 +30,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
go install github.com/beeper/pre-commit-go/bridgelint@latest
|
|
||||||
export PATH="$HOME/go/bin:$PATH"
|
export PATH="$HOME/go/bin:$PATH"
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,9 @@ repos:
|
||||||
- id: go-mod-tidy
|
- id: go-mod-tidy
|
||||||
|
|
||||||
- repo: https://github.com/beeper/pre-commit-go
|
- repo: https://github.com/beeper/pre-commit-go
|
||||||
rev: v0.5.0
|
rev: v0.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: zerolog-ban-msgf
|
- id: zerolog-ban-msgf
|
||||||
- id: zerolog-use-stringer
|
- id: zerolog-use-stringer
|
||||||
- id: prevent-literal-http-methods
|
- id: prevent-literal-http-methods
|
||||||
- id: zerolog-ban-global-log
|
- id: zerolog-ban-global-log
|
||||||
- id: bridgelint
|
|
||||||
language: system
|
|
||||||
verbose: true
|
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -14,13 +14,13 @@ require (
|
||||||
github.com/yuin/goldmark v1.8.6
|
github.com/yuin/goldmark v1.8.6
|
||||||
go.mau.fi/util v0.11.0
|
go.mau.fi/util v0.11.0
|
||||||
go.mau.fi/webp v0.3.0
|
go.mau.fi/webp v0.3.0
|
||||||
go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a
|
go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2
|
||||||
golang.org/x/image v0.47.0
|
golang.org/x/image v0.47.0
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.61.0
|
||||||
golang.org/x/sync v0.24.0
|
golang.org/x/sync v0.24.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.32.1-0.20261105141340-842796acf445
|
maunium.net/go/mautrix v0.32.1-0.20261025192835-58302644df53
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -71,8 +71,8 @@ go.mau.fi/util v0.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY=
|
||||||
go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s=
|
go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s=
|
||||||
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-20261026125351-1fbae988179a h1:ZyCV/mWfRkutxs5R0K3v+oyTbF4huprSlPZg/LEFQ7k=
|
go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2 h1:+EhsWM5VeE5Vu3KRlk3PmYAY0lEYkbhI5W4mTmM9X/Q=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ=
|
go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ=
|
||||||
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.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
||||||
|
|
@ -103,5 +103,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.32.1-0.20261105141340-842796acf445 h1:ZI9Y2YEmjqQDloOCiciHeynTRYjUwK0ZZbNcRH/5NOc=
|
maunium.net/go/mautrix v0.32.1-0.20261025192835-58302644df53 h1:bL8PdtUUkpuHNO+r7fnyzrSUk3b8d7+f/E/r1HInRkg=
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261105141340-842796acf445/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU=
|
maunium.net/go/mautrix v0.32.1-0.20261025192835-58302644df53/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU=
|
||||||
|
|
|
||||||
|
|
@ -142,16 +142,8 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context,
|
||||||
Str("peer_data_request_session_id", evt.GetPeerDataRequestSessionID()).
|
Str("peer_data_request_session_id", evt.GetPeerDataRequestSessionID()).
|
||||||
Msg("Downloading history sync")
|
Msg("Downloading history sync")
|
||||||
blob, err := wa.Client.DownloadHistorySync(log.WithContext(ctx), evt, true)
|
blob, err := wa.Client.DownloadHistorySync(log.WithContext(ctx), evt, true)
|
||||||
if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
|
if err != nil {
|
||||||
log.Err(err).Msg("History sync blob is no longer available, dropping notification")
|
|
||||||
if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil {
|
|
||||||
log.Err(err).Msg("Failed to delete queued history sync notification")
|
|
||||||
errored = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
log.Err(err).Msg("Failed to download history sync")
|
log.Err(err).Msg("Failed to download history sync")
|
||||||
errored = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND {
|
if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND {
|
||||||
|
|
@ -179,7 +171,6 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to store history sync notification data")
|
log.Err(err).Msg("Failed to store history sync notification data")
|
||||||
errored = true
|
|
||||||
} else {
|
} else {
|
||||||
resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP ||
|
resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP ||
|
||||||
blob.GetSyncType() == waHistorySync.HistorySync_RECENT ||
|
blob.GetSyncType() == waHistorySync.HistorySync_RECENT ||
|
||||||
|
|
@ -255,17 +246,10 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
} else if jid.Server == types.BroadcastServer {
|
} else if jid.Server == types.BroadcastServer {
|
||||||
log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync")
|
log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync")
|
||||||
continue
|
continue
|
||||||
} else if jid == types.PSAJID {
|
|
||||||
// New messages in the official updates chat aren't bridged because they use a different path,
|
|
||||||
// so drop history as well. This should be removed if the PSA chat gets properly bridged in the future.
|
|
||||||
log.Debug().Stringer("chat_jid", jid).Msg("Skipping PSA in history sync")
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
totalMessageCount += len(conv.GetMessages())
|
totalMessageCount += len(conv.GetMessages())
|
||||||
}
|
}
|
||||||
if jid == types.MetaAIJID {
|
if jid.Server == types.DefaultUserServer {
|
||||||
jid = types.NewMetaAIJID
|
|
||||||
} else if jid.Server == types.DefaultUserServer {
|
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync")
|
log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync")
|
||||||
|
|
@ -298,9 +282,6 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
Int("msg_index", i).
|
Int("msg_index", i).
|
||||||
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()).
|
||||||
Stringer("stub_type", rawMsg.GetMessage().GetMessageStubType()).
|
|
||||||
Str("message_type", getMessageType(rawMsg.GetMessage().GetMessage())).
|
|
||||||
Bool("from_me", rawMsg.GetMessage().GetKey().GetFromMe()).
|
|
||||||
Msg("Dropping historical message due to parse error")
|
Msg("Dropping historical message due to parse error")
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Any("web_message_info", rawMsg.GetMessage()).
|
Any("web_message_info", rawMsg.GetMessage()).
|
||||||
|
|
@ -454,7 +435,7 @@ 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) || errors.Is(err, ErrNewsletterNotFound) {
|
if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) {
|
||||||
log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID).
|
log.Debug().Err(err).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)
|
||||||
|
|
@ -605,36 +586,16 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true, !params.Forward && !hasMore)
|
resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert messages: %w", err)
|
return nil, fmt.Errorf("failed to convert messages: %w", err)
|
||||||
}
|
}
|
||||||
// TODO might be better to always set true here and let GetBetween return an empty result to mark as false?
|
|
||||||
resp.HasMore = hasMore
|
resp.HasMore = hasMore
|
||||||
resp.Forward = params.Forward
|
resp.Forward = params.Forward
|
||||||
resp.MarkRead = markRead
|
resp.MarkRead = markRead
|
||||||
if params.Forward && !markRead && conv != nil && !ptr.Val(conv.MarkedAsUnread) {
|
|
||||||
resp.ReadUpTo = historySyncReadUpTo(resp.Messages, int(ptr.Val(conv.UnreadCount)))
|
|
||||||
}
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func historySyncReadUpTo(messages []*bridgev2.BackfillMessage, unreadCount int) time.Time {
|
|
||||||
if unreadCount <= 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
for i := len(messages) - 1; i >= 0; i-- {
|
|
||||||
if messages[i].Sender.IsFromMe {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
unreadCount--
|
|
||||||
if unreadCount == 0 {
|
|
||||||
return messages[i].Timestamp.Add(-time.Nanosecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) {
|
func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) {
|
||||||
var err error
|
var err error
|
||||||
var rows int64
|
var rows int64
|
||||||
|
|
@ -667,7 +628,6 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
portalJID types.JID,
|
portalJID types.JID,
|
||||||
messages []*waWeb.WebMessageInfo,
|
messages []*waWeb.WebMessageInfo,
|
||||||
explodeOnError bool,
|
explodeOnError bool,
|
||||||
deleteEverything bool,
|
|
||||||
) (*bridgev2.FetchMessagesResponse, error) {
|
) (*bridgev2.FetchMessagesResponse, error) {
|
||||||
cli := wa.Client
|
cli := wa.Client
|
||||||
if cli == nil {
|
if cli == nil {
|
||||||
|
|
@ -704,9 +664,6 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, false) {
|
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, false) {
|
||||||
return nil, fmt.Errorf("failed to ensure alt JIDs for message %s", evt.Info.ID)
|
return nil, fmt.Errorf("failed to ensure alt JIDs for message %s", evt.Info.ID)
|
||||||
}
|
}
|
||||||
if evt.Info.IsFromMe {
|
|
||||||
wa.addPreviousOwnLID(ctx, evt.Info.Sender)
|
|
||||||
}
|
|
||||||
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
||||||
converted, mediaReq := wa.convertHistorySyncMessage(
|
converted, mediaReq := wa.convertHistorySyncMessage(
|
||||||
ctx, cli, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions,
|
ctx, cli, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions,
|
||||||
|
|
@ -726,11 +683,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
Messages: convertedMessages,
|
Messages: convertedMessages,
|
||||||
Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)),
|
Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)),
|
||||||
CompleteCallback: func() {
|
CompleteCallback: func() {
|
||||||
if deleteEverything {
|
// TODO this only deletes after backfilling. If there's no need for backfill after a relogin,
|
||||||
wa.deleteHistorySyncMessages(ctx, portalJID, 1<<62, oldestTS)
|
// the messages will be stuck in the database
|
||||||
} else {
|
|
||||||
wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS)
|
wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS)
|
||||||
}
|
|
||||||
if len(mediaRequests) > 0 {
|
if len(mediaRequests) > 0 {
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
for _, req := range mediaRequests {
|
for _, req := range mediaRequests {
|
||||||
|
|
@ -811,12 +766,10 @@ type onDemandHistorySyncRequest struct {
|
||||||
SentAt time.Time
|
SentAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDemandHistorySyncRequestTimeout = 1 * time.Hour
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) startOnDemandHistorySyncRequest(ctx context.Context, portalJID types.JID, msgID types.MessageID) bool {
|
func (wa *WhatsAppClient) startOnDemandHistorySyncRequest(ctx context.Context, portalJID types.JID, msgID types.MessageID) bool {
|
||||||
wa.onDemandHistorySyncLock.Lock()
|
wa.onDemandHistorySyncLock.Lock()
|
||||||
defer wa.onDemandHistorySyncLock.Unlock()
|
defer wa.onDemandHistorySyncLock.Unlock()
|
||||||
if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight && time.Since(req.SentAt) < onDemandHistorySyncRequestTimeout {
|
if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight {
|
||||||
zerolog.Ctx(ctx).Debug().
|
zerolog.Ctx(ctx).Debug().
|
||||||
Str("in_flight_request_msg_id", req.ID).
|
Str("in_flight_request_msg_id", req.ID).
|
||||||
Stringer("portal_jid", portalJID).
|
Stringer("portal_jid", portalJID).
|
||||||
|
|
@ -850,9 +803,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
||||||
zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID")
|
zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if portalJID == types.MetaAIJID {
|
if portalJID.Server == types.DefaultUserServer {
|
||||||
portalJID = types.NewMetaAIJID
|
|
||||||
} else if portalJID.Server == types.DefaultUserServer {
|
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, portalJID)
|
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, portalJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Stringer("lid", portalJID).Msg("Failed to get LID for phone number in on-demand history sync")
|
zerolog.Ctx(ctx).Err(err).Stringer("lid", portalJID).Msg("Failed to get LID for phone number in on-demand history sync")
|
||||||
|
|
@ -893,7 +844,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
||||||
Int("message_count", len(messages)).
|
Int("message_count", len(messages)).
|
||||||
Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()).
|
Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()).
|
||||||
Msg("Converting messages to bridge from on-demand history sync")
|
Msg("Converting messages to bridge from on-demand history sync")
|
||||||
resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false, false)
|
resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrBroadcastList = errors.New("broadcast list bridging is currently not supported")
|
var ErrBroadcastList = errors.New("broadcast list bridging is currently not supported")
|
||||||
var ErrNewsletterNotFound = errors.New("newsletter doesn't exist")
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) {
|
func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) {
|
||||||
switch portalJID.Server {
|
switch portalJID.Server {
|
||||||
|
|
@ -57,8 +56,6 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID,
|
||||||
info, err := wa.Client.GetNewsletterInfo(ctx, portalJID)
|
info, err := wa.Client.GetNewsletterInfo(ctx, portalJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if info == nil || info.ID.IsEmpty() {
|
|
||||||
return nil, ErrNewsletterNotFound
|
|
||||||
}
|
}
|
||||||
wrapped = wa.wrapNewsletterInfo(ctx, info)
|
wrapped = wa.wrapNewsletterInfo(ctx, info)
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
||||||
mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle),
|
mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle),
|
||||||
pushNamesSynced: exsync.NewEvent(),
|
pushNamesSynced: exsync.NewEvent(),
|
||||||
appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time),
|
appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time),
|
||||||
previousOwnLIDs: exsync.NewSetWithItems(login.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs),
|
|
||||||
|
|
||||||
onDemandHistorySyncRequests: make(map[types.JID]onDemandHistorySyncRequest),
|
onDemandHistorySyncRequests: make(map[types.JID]onDemandHistorySyncRequest),
|
||||||
|
|
||||||
|
|
@ -85,7 +84,6 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
||||||
w.Client.ManualHistorySyncDownload = true
|
w.Client.ManualHistorySyncDownload = true
|
||||||
w.Client.SendReportingTokens = true
|
w.Client.SendReportingTokens = true
|
||||||
w.Client.AutomaticMessageRerequestFromPhone = true
|
w.Client.AutomaticMessageRerequestFromPhone = true
|
||||||
w.Client.UnifiedSessionInterval = 24 * time.Hour
|
|
||||||
w.Client.GetMessageForRetry = w.trackNotFoundRetry
|
w.Client.GetMessageForRetry = w.trackNotFoundRetry
|
||||||
w.Client.PreRetryCallback = w.trackFoundRetry
|
w.Client.PreRetryCallback = w.trackFoundRetry
|
||||||
w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx)
|
w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx)
|
||||||
|
|
@ -139,9 +137,6 @@ type WhatsAppClient struct {
|
||||||
|
|
||||||
onDemandHistorySyncLock sync.Mutex
|
onDemandHistorySyncLock sync.Mutex
|
||||||
onDemandHistorySyncRequests map[types.JID]onDemandHistorySyncRequest
|
onDemandHistorySyncRequests map[types.JID]onDemandHistorySyncRequest
|
||||||
|
|
||||||
previousOwnLIDs *exsync.Set[types.JID]
|
|
||||||
previousOwnLIDsLock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -210,26 +205,7 @@ func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID)
|
||||||
|
|
||||||
func (wa *WhatsAppClient) IsOwnJID(jid types.JID) bool {
|
func (wa *WhatsAppClient) IsOwnJID(jid types.JID) bool {
|
||||||
return (jid.Server == types.DefaultUserServer && jid.User == wa.JID.User) ||
|
return (jid.Server == types.DefaultUserServer && jid.User == wa.JID.User) ||
|
||||||
(jid.Server == types.HiddenUserServer && jid.User == wa.GetLID().User) ||
|
(jid.Server == types.HiddenUserServer && jid.User == wa.GetLID().User)
|
||||||
wa.previousOwnLIDs.Has(jid.ToNonAD())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) addPreviousOwnLID(ctx context.Context, jid types.JID) {
|
|
||||||
if jid.Server != types.HiddenUserServer || wa.IsOwnJID(jid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jid = jid.ToNonAD()
|
|
||||||
wa.previousOwnLIDsLock.Lock()
|
|
||||||
defer wa.previousOwnLIDsLock.Unlock()
|
|
||||||
if !wa.previousOwnLIDs.Add(jid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
zerolog.Ctx(ctx).Info().Stringer("lid", jid).Msg("Found previous own LID in history sync")
|
|
||||||
wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs = wa.previousOwnLIDs.AsList()
|
|
||||||
err := wa.UserLogin.Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save user login after adding previous own LID")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) GetLID() types.JID {
|
func (wa *WhatsAppClient) GetLID() types.JID {
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,13 @@ func fnSync(ce *commands.Event) {
|
||||||
case "appstate":
|
case "appstate":
|
||||||
names := appstate.AllPatchNames[:]
|
names := appstate.AllPatchNames[:]
|
||||||
if len(ce.Args) > 1 {
|
if len(ce.Args) > 1 {
|
||||||
names = parseAppStateNames(ce, ce.Args[1:])
|
names = exslices.CastFuncFilter(ce.Args[1:], func(name string) (appstate.WAPatchName, bool) {
|
||||||
|
if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) {
|
||||||
|
ce.Reply("Invalid app state name `%s`", name)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return appstate.WAPatchName(name), true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
err := wa.Client.FetchAppState(ce.Ctx, name, true, false)
|
err := wa.Client.FetchAppState(ce.Ctx, name, true, false)
|
||||||
|
|
@ -168,50 +174,6 @@ func fnSync(ce *commands.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAppStateNames(ce *commands.Event, args []string) []appstate.WAPatchName {
|
|
||||||
return exslices.CastFuncFilter(args, func(name string) (appstate.WAPatchName, bool) {
|
|
||||||
if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) {
|
|
||||||
ce.Reply("Invalid app state name `%s`", name)
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return appstate.WAPatchName(name), true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdResetAppState = &commands.FullHandler{
|
|
||||||
Func: fnResetAppState,
|
|
||||||
Name: "reset-appstate",
|
|
||||||
Help: commands.HelpMeta{
|
|
||||||
Section: commands.HelpSectionAdmin,
|
|
||||||
Description: "Ask your phone to reset broken app state. This logs out all linked devices.",
|
|
||||||
Args: "<names...>",
|
|
||||||
},
|
|
||||||
RequiresLogin: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func fnResetAppState(ce *commands.Event) {
|
|
||||||
login := ce.User.GetDefaultLogin()
|
|
||||||
if login == nil {
|
|
||||||
ce.Reply("Login not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ce.Args) == 0 {
|
|
||||||
ce.Reply("Usage: `$cmdprefix reset-appstate <names...>`\n\nThis logs out all linked devices, including the bridge. You'll have to log in again afterwards.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
names := parseAppStateNames(ce, ce.Args)
|
|
||||||
if len(names) != len(ce.Args) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wa := login.Client.(*WhatsAppClient)
|
|
||||||
_, err := wa.Client.SendPeerMessage(ce.Ctx, whatsmeow.BuildFatalAppStateExceptionNotification(names...))
|
|
||||||
if err != nil {
|
|
||||||
ce.Reply("Failed to send app state reset request: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ce.Reply("Sent app state reset request. Your phone should log out all linked devices shortly, after which you can log in again.")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdInviteLink = &commands.FullHandler{
|
var cmdInviteLink = &commands.FullHandler{
|
||||||
Func: fnInviteLink,
|
Func: fnInviteLink,
|
||||||
Name: "invite-link",
|
Name: "invite-link",
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger())
|
wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger())
|
||||||
wa.MsgConv.DB = wa.DB
|
wa.MsgConv.DB = wa.DB
|
||||||
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
|
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
|
||||||
cmdAccept, cmdSync, cmdResetAppState, cmdInviteLink, cmdResolveLink, cmdJoin,
|
cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin,
|
||||||
)
|
)
|
||||||
wa.mediaEditCache = make(MediaEditCache)
|
wa.mediaEditCache = make(MediaEditCache)
|
||||||
wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]()
|
wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]()
|
||||||
|
|
@ -164,19 +164,11 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to migrate to LID DMs: %w", err)
|
return fmt.Errorf("failed to migrate to LID DMs: %w", err)
|
||||||
}
|
}
|
||||||
err = wa.migrateMetaAIPortals(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to migrate Meta AI portals: %w", err)
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx)
|
err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts")
|
wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts")
|
||||||
}
|
}
|
||||||
err = wa.removeRejoinedPNGhosts(wa.Bridge.BackgroundCtx)
|
|
||||||
if err != nil {
|
|
||||||
wa.Bridge.Log.Err(err).Msg("Failed to remove rejoined PN ghosts")
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -243,16 +235,7 @@ func (wa *WhatsAppConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) ResetHTTPTransport() {
|
func (wa *WhatsAppConnector) ResetHTTPTransport() {
|
||||||
for _, login := range wa.Bridge.GetAllCachedUserLogins() {
|
// No-op for now, whatsmeow doesn't use the shared transport config yet
|
||||||
client := login.Client.(*WhatsAppClient).Client
|
|
||||||
if client == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := wa.updateProxy(login.Log.WithContext(wa.Bridge.BackgroundCtx), client, false)
|
|
||||||
if err != nil {
|
|
||||||
login.Log.Err(err).Msg("Failed to recreate HTTP clients")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) ResetNetworkConnections() {
|
func (wa *WhatsAppConnector) ResetNetworkConnections() {
|
||||||
|
|
|
||||||
|
|
@ -164,19 +164,7 @@ func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, par
|
||||||
return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin)
|
return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin)
|
||||||
}
|
}
|
||||||
sticker, err := wa.MsgConv.GetCachedSticker(ctx, waClient.Client, parsedID.Sticker.PackID, parsedID.Sticker.FileHash)
|
sticker, err := wa.MsgConv.GetCachedSticker(ctx, waClient.Client, parsedID.Sticker.PackID, parsedID.Sticker.FileHash)
|
||||||
var httpErr whatsmeow.DownloadHTTPError
|
if err != nil {
|
||||||
if errors.As(err, &httpErr) {
|
|
||||||
switch httpErr.StatusCode {
|
|
||||||
case http.StatusNotFound, http.StatusGone:
|
|
||||||
return nil, mautrix.MNotFound.WithMessage("Sticker pack not found")
|
|
||||||
case http.StatusUnauthorized, http.StatusForbidden:
|
|
||||||
return nil, mautrix.MForbidden.WithMessage("WhatsApp denied access to the sticker pack")
|
|
||||||
case http.StatusTooManyRequests:
|
|
||||||
return nil, mautrix.MLimitExceeded.WithMessage("Rate limited by WhatsApp while fetching the sticker pack").WithCanRetry(true)
|
|
||||||
default:
|
|
||||||
return nil, mautrix.MUnknown.WithMessage("WhatsApp returned HTTP %d while fetching the sticker pack", httpErr.StatusCode).WithStatus(http.StatusBadGateway).WithCanRetry(true)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if sticker == nil {
|
} else if sticker == nil {
|
||||||
return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack")
|
return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack")
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// wrapConversionError turns a failure to convert a Matrix event into a message status. Nothing has been
|
// wrapConversionError turns a failure to convert a Matrix event into a message status. Nothing has been
|
||||||
|
|
@ -90,50 +85,6 @@ func wrapWhatsAppError(err error) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapSendError turns an error from sending a message to WhatsApp into a message status.
|
|
||||||
func (wa *WhatsAppClient) wrapSendError(err error) error {
|
|
||||||
switch sendErrorCode(err) {
|
|
||||||
case 403:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("you don't have permission to do that on WhatsApp").
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case 463:
|
|
||||||
msg := "WhatsApp has temporarily restricted your account from messaging new contacts"
|
|
||||||
if until := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil; until.After(time.Now()) {
|
|
||||||
msg = fmt.Sprintf("WhatsApp has restricted your account from messaging new contacts until %s", until.UTC().Format("Jan 2, 2006 at 3:04 PM MST"))
|
|
||||||
}
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage(msg).
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case 421, 479:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("WhatsApp rejected the message").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
default:
|
|
||||||
return wrapWhatsAppError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendErrorCode returns the code WhatsApp rejected a message with, which whatsmeow only includes in the
|
|
||||||
// error text.
|
|
||||||
func sendErrorCode(err error) int {
|
|
||||||
if !errors.Is(err, whatsmeow.ErrServerReturnedError) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
_, codeStr, _ := strings.Cut(err.Error(), whatsmeow.ErrServerReturnedError.Error()+" ")
|
|
||||||
code, _ := strconv.Atoi(codeStr)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapWhatsAppRespError turns an error from a whatsmeow request made during login or provisioning into
|
// wrapWhatsAppRespError turns an error from a whatsmeow request made during login or provisioning into
|
||||||
// an HTTP error, keeping the original error in the chain. Clients retry 429 and 502-504 automatically,
|
// an HTTP error, keeping the original error in the chain. Clients retry 429 and 502-504 automatically,
|
||||||
// which isn't wanted for provisioning requests, so those statuses aren't used.
|
// which isn't wanted for provisioning requests, so those statuses aren't used.
|
||||||
|
|
|
||||||
|
|
@ -474,9 +474,7 @@ func (evt *WAMediaRetry) getRealSender() types.JID {
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetSender() bridgev2.EventSender {
|
func (evt *WAMediaRetry) GetSender() bridgev2.EventSender {
|
||||||
realSender := pickLID(evt.getRealSender(), evt.senderLID)
|
realSender := pickLID(evt.getRealSender(), evt.senderLID)
|
||||||
sender := evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender)
|
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender)
|
||||||
sender.ForceEditOrigSender = true
|
|
||||||
return sender
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID {
|
func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID {
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,6 @@ func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2
|
||||||
var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail)
|
var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail)
|
||||||
var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail)
|
var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail)
|
||||||
var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail)
|
var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail)
|
||||||
var ErrEmptyGroupName = bridgev2.WrapErrorInStatus(errors.New("WhatsApp group names can't be empty")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail)
|
|
||||||
var ErrAddBlockedByPrivacy = bridgev2.WrapErrorInStatus(errors.New("this user's privacy settings don't allow adding them to groups")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail)
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) {
|
func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
|
|
@ -141,7 +139,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
||||||
if errors.Is(err, whatsmeow.ErrNoPermissionInGroup) {
|
if errors.Is(err, whatsmeow.ErrNoPermissionInGroup) {
|
||||||
return nil, ErrNotCommunityAdmin
|
return nil, ErrNotCommunityAdmin
|
||||||
}
|
}
|
||||||
return nil, wa.wrapSendError(err)
|
return nil, wrapWhatsAppError(err)
|
||||||
}
|
}
|
||||||
var pickedMessageID networkid.MessageID
|
var pickedMessageID networkid.MessageID
|
||||||
if resp.Sender == wa.GetLID() {
|
if resp.Sender == wa.GetLID() {
|
||||||
|
|
@ -220,7 +218,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev
|
||||||
Metadata: &waid.ReactionMetadata{
|
Metadata: &waid.ReactionMetadata{
|
||||||
SenderDeviceID: wa.JID.Device,
|
SenderDeviceID: wa.JID.Device,
|
||||||
},
|
},
|
||||||
}, wa.wrapSendError(err)
|
}, wrapWhatsAppError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) {
|
func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) {
|
||||||
|
|
@ -250,7 +248,7 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
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 wa.wrapSendError(err)
|
return wrapWhatsAppError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) {
|
func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) {
|
||||||
|
|
@ -290,7 +288,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M
|
||||||
ID: editID,
|
ID: editID,
|
||||||
})
|
})
|
||||||
log.Trace().Any("response", resp).Msg("WhatsApp edit response")
|
log.Trace().Any("response", resp).Msg("WhatsApp edit response")
|
||||||
return wa.wrapSendError(err)
|
return wrapWhatsAppError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) {
|
func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) {
|
||||||
|
|
@ -315,11 +313,11 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br
|
||||||
|
|
||||||
resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage, extra)
|
resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage, extra)
|
||||||
log.Trace().Any("response", resp).Msg("WhatsApp delete response")
|
log.Trace().Any("response", resp).Msg("WhatsApp delete response")
|
||||||
return wa.wrapSendError(err)
|
return wrapWhatsAppError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) {
|
func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) {
|
||||||
if receipt.Receipt.IsThread() || !receipt.ReadUpTo.After(receipt.LastRead) {
|
if !receipt.ReadUpTo.After(receipt.LastRead) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if receipt.LastRead.IsZero() {
|
if receipt.LastRead.IsZero() {
|
||||||
|
|
@ -485,8 +483,6 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg
|
||||||
return nil, wrapWhatsAppError(err)
|
return nil, wrapWhatsAppError(err)
|
||||||
} else if len(resp) == 0 {
|
} else if len(resp) == 0 {
|
||||||
return nil, fmt.Errorf("no response for participant change")
|
return nil, fmt.Errorf("no response for participant change")
|
||||||
} else if resp[0].Error == 403 && action == whatsmeow.ParticipantChangeAdd {
|
|
||||||
return nil, ErrAddBlockedByPrivacy
|
|
||||||
} else if resp[0].Error != 0 {
|
} else if resp[0].Error != 0 {
|
||||||
return nil, fmt.Errorf("failed to change participant: code %d", resp[0].Error)
|
return nil, fmt.Errorf("failed to change participant: code %d", resp[0].Error)
|
||||||
}
|
}
|
||||||
|
|
@ -505,8 +501,6 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev
|
||||||
|
|
||||||
if msg.Portal.RoomType == database.RoomTypeDM {
|
if msg.Portal.RoomType == database.RoomTypeDM {
|
||||||
return false, fmt.Errorf("cannot set room name for DM")
|
return false, fmt.Errorf("cannot set room name for DM")
|
||||||
} else if msg.Content.Name == "" {
|
|
||||||
return false, ErrEmptyGroupName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
defer wa.mcTrack(msg, time.Now(), &retErr)
|
||||||
|
|
|
||||||
|
|
@ -237,11 +237,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
})
|
})
|
||||||
wa.notifyOfflineSyncWaiter(fmt.Errorf("stream error: %s", message))
|
wa.notifyOfflineSyncWaiter(fmt.Errorf("stream error: %s", message))
|
||||||
case *events.StreamReplaced:
|
case *events.StreamReplaced:
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: WAStreamReplaced})
|
||||||
StateEvent: status.StateUnknownError,
|
|
||||||
Error: WAStreamReplaced,
|
|
||||||
BlockAutoReconnect: true,
|
|
||||||
})
|
|
||||||
wa.notifyOfflineSyncWaiter(fmt.Errorf("stream replaced"))
|
wa.notifyOfflineSyncWaiter(fmt.Errorf("stream replaced"))
|
||||||
case *events.KeepAliveTimeout:
|
case *events.KeepAliveTimeout:
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAKeepaliveTimeout})
|
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAKeepaliveTimeout})
|
||||||
|
|
@ -257,11 +253,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
wa.notifyOfflineSyncWaiter(fmt.Errorf("connection failure: %s (%s)", evt.Reason, evt.Message))
|
wa.notifyOfflineSyncWaiter(fmt.Errorf("connection failure: %s (%s)", evt.Reason, evt.Message))
|
||||||
case *events.ClientOutdated:
|
case *events.ClientOutdated:
|
||||||
wa.UserLogin.Log.Error().Msg("Got a client outdated connect failure. The bridge is likely out of date, please update immediately.")
|
wa.UserLogin.Log.Error().Msg("Got a client outdated connect failure. The bridge is likely out of date, please update immediately.")
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: WAClientOutdated})
|
||||||
StateEvent: status.StateUnknownError,
|
|
||||||
Error: WAClientOutdated,
|
|
||||||
BlockAutoReconnect: true,
|
|
||||||
})
|
|
||||||
wa.notifyOfflineSyncWaiter(fmt.Errorf("client outdated"))
|
wa.notifyOfflineSyncWaiter(fmt.Errorf("client outdated"))
|
||||||
case *events.TemporaryBan:
|
case *events.TemporaryBan:
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
||||||
|
|
@ -374,14 +366,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
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, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey)
|
protocolMsg, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey)
|
||||||
if protocolMsg.GetEditedMessage() == nil {
|
|
||||||
wa.UserLogin.Log.Debug().
|
|
||||||
Str("message_id", evt.Info.ID).
|
|
||||||
Str("parent_id", parentKey.GetID()).
|
|
||||||
Stringer("assoc_type", assocType).
|
|
||||||
Msg("Ignoring HD replacement message without replacement content")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dontRenderEdited = shouldHideEdit
|
dontRenderEdited = shouldHideEdit
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Str("message_id", evt.Info.ID).
|
Str("message_id", evt.Info.ID).
|
||||||
|
|
@ -530,10 +514,6 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
||||||
} else if evt.Chat.Server == types.NewsletterServer {
|
} else if evt.Chat.Server == types.NewsletterServer {
|
||||||
messageSender = evt.Chat
|
messageSender = evt.Chat
|
||||||
}
|
}
|
||||||
var messageSenderAlt types.JID
|
|
||||||
if evt.MessageSender.IsEmpty() && evt.Chat.Server == types.BotServer {
|
|
||||||
messageSenderAlt = wa.JID
|
|
||||||
}
|
|
||||||
var chatAlt types.JID
|
var chatAlt types.JID
|
||||||
if evt.Chat.Server == types.DefaultUserServer {
|
if evt.Chat.Server == types.DefaultUserServer {
|
||||||
chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat)
|
chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat)
|
||||||
|
|
@ -547,9 +527,6 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
||||||
if !chatAlt.IsEmpty() {
|
if !chatAlt.IsEmpty() {
|
||||||
targets = append(targets, waid.MakeMessageID(chatAlt, messageSender, id))
|
targets = append(targets, waid.MakeMessageID(chatAlt, messageSender, id))
|
||||||
}
|
}
|
||||||
if !messageSenderAlt.IsEmpty() {
|
|
||||||
targets = append(targets, waid.MakeMessageID(evt.Chat, messageSenderAlt, id))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
senderLID := evt.Sender
|
senderLID := evt.Sender
|
||||||
if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() {
|
if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() {
|
||||||
|
|
@ -978,9 +955,7 @@ func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt
|
||||||
defer wa.appStateRecoveryLock.Unlock()
|
defer wa.appStateRecoveryLock.Unlock()
|
||||||
meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
|
meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
|
||||||
if ts, exists := meta.AppStateRecoveryAttempted[evt.Name]; exists {
|
if ts, exists := meta.AppStateRecoveryAttempted[evt.Name]; exists {
|
||||||
if !evt.Recovery {
|
|
||||||
delete(wa.appStateFullSyncAttempted, evt.Name)
|
delete(wa.appStateFullSyncAttempted, evt.Name)
|
||||||
}
|
|
||||||
delete(meta.AppStateRecoveryAttempted, evt.Name)
|
delete(meta.AppStateRecoveryAttempted, evt.Name)
|
||||||
err := wa.UserLogin.Save(ctx)
|
err := wa.UserLogin.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1018,7 +993,7 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !evt.FullSync {
|
if !evt.FullSync {
|
||||||
if !lastFullSync.IsZero() && time.Since(lastFullSync) < 48*time.Hour {
|
if !lastFullSync.IsZero() {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Err(evt.Error).
|
Err(evt.Error).
|
||||||
Time("last_full_sync_attempt", lastFullSync).
|
Time("last_full_sync_attempt", lastFullSync).
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -26,11 +25,9 @@ import (
|
||||||
"go.mau.fi/util/dbutil"
|
"go.mau.fi/util/dbutil"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
||||||
)
|
)
|
||||||
|
|
@ -50,8 +47,6 @@ func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg ne
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if parsed.Chat == types.NewMetaAIJID {
|
|
||||||
altChat = types.MetaAIJID
|
|
||||||
}
|
}
|
||||||
if !altSender.IsEmpty() {
|
if !altSender.IsEmpty() {
|
||||||
altSenderID := *parsed
|
altSenderID := *parsed
|
||||||
|
|
@ -63,8 +58,6 @@ func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg ne
|
||||||
altChatID.Chat = altChat
|
altChatID.Chat = altChat
|
||||||
if altSender.Server == types.DefaultUserServer {
|
if altSender.Server == types.DefaultUserServer {
|
||||||
altChatID.Sender = altSender
|
altChatID.Sender = altSender
|
||||||
} else if parsed.Sender == types.NewMetaAIJID {
|
|
||||||
altChatID.Sender = types.MetaAIJID
|
|
||||||
}
|
}
|
||||||
alts = append(alts, altChatID.String())
|
alts = append(alts, altChatID.String())
|
||||||
}
|
}
|
||||||
|
|
@ -218,30 +211,6 @@ func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) migrateMetaAIPortals(ctx context.Context) error {
|
|
||||||
if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_meta_ai_portals_migrated") == "true" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log := zerolog.Ctx(ctx).With().Str("action", "migrate meta ai portals").Logger()
|
|
||||||
const findMetaAIPortals = "SELECT id, receiver FROM portal WHERE bridge_id=$1 AND id=$2"
|
|
||||||
portalKeys, err := scanPortalKey.NewRowIter(wa.Bridge.DB.Query(ctx, findMetaAIPortals, wa.Bridge.ID, waid.MakePortalID(types.MetaAIJID))).AsList()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get Meta AI portals: %w", err)
|
|
||||||
}
|
|
||||||
for _, key := range portalKeys {
|
|
||||||
res, err := wa.reIDPhoneDMToLID(ctx, types.MetaAIJID, types.NewMetaAIJID, key.Receiver)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to re-ID Meta AI portal for %s: %w", key.Receiver, err)
|
|
||||||
}
|
|
||||||
log.Info().
|
|
||||||
Str("receiver", string(key.Receiver)).
|
|
||||||
Stringer("result", res).
|
|
||||||
Msg("Re-ID'd Meta AI phone number portal")
|
|
||||||
}
|
|
||||||
wa.Bridge.DB.KV.Set(ctx, "whatsapp_meta_ai_portals_migrated", "true")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error {
|
func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error {
|
||||||
if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_avatars_resynced") == "true" {
|
if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_avatars_resynced") == "true" {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -298,84 +267,3 @@ func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error {
|
||||||
wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_avatars_resynced", "true")
|
wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_avatars_resynced", "true")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error {
|
|
||||||
if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dm_pn_ghosts_removed_v2") == "true" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
stateGetter, ok := wa.Bridge.Matrix.(bridgev2.MatrixConnectorWithArbitraryRoomState)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log := zerolog.Ctx(ctx).With().Str("action", "remove rejoined pn ghosts").Logger()
|
|
||||||
const findLIDPortals = "SELECT id, mxid FROM portal WHERE bridge_id=$1 AND room_type='dm' AND id LIKE '%@lid' AND mxid IS NOT NULL"
|
|
||||||
lidPortals, err := scanLIDPortal.NewRowIter(wa.Bridge.DB.Query(ctx, findLIDPortals, wa.Bridge.ID)).AsList()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get LID portals: %w", err)
|
|
||||||
}
|
|
||||||
var removed, failed int
|
|
||||||
for _, portal := range lidPortals {
|
|
||||||
lid, err := waid.ParsePortalID(portal.ID)
|
|
||||||
if err != nil || lid.Server != types.HiddenUserServer {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pn, err := wa.DeviceStore.LIDMap.GetPNForLID(ctx, lid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get PN for LID portal %s: %w", portal.ID, err)
|
|
||||||
} else if pn.IsEmpty() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pnUserID := waid.MakeUserID(pn)
|
|
||||||
memberEvt, err := stateGetter.GetStateEvent(ctx, portal.MXID, event.StateMember, wa.Bridge.Matrix.FormatGhostMXID(pnUserID).String())
|
|
||||||
if errors.Is(err, mautrix.MNotFound) || errors.Is(err, mautrix.MForbidden) {
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
log.Warn().Err(err).Stringer("room_id", portal.MXID).Msg("Failed to get PN ghost membership")
|
|
||||||
failed++
|
|
||||||
continue
|
|
||||||
} else if memberEvt == nil || memberEvt.Content.AsMember().Membership != event.MembershipJoin {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pnGhost, err := wa.Bridge.GetGhostByID(ctx, pnUserID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get PN ghost for %s: %w", pn, err)
|
|
||||||
}
|
|
||||||
_, err = pnGhost.Intent.SendState(ctx, portal.MXID, event.StateMember, pnGhost.Intent.GetMXID().String(), &event.Content{
|
|
||||||
Parsed: &event.MemberEventContent{Membership: event.MembershipLeave, Reason: "Migrating to LIDs"},
|
|
||||||
Raw: map[string]any{"com.beeper.exclude_from_timeline": true},
|
|
||||||
}, time.Time{})
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Stringer("room_id", portal.MXID).Stringer("pn", pn).Msg("Failed to remove PN ghost from LID DM")
|
|
||||||
if !errors.Is(err, mautrix.MForbidden) && !errors.Is(err, mautrix.MNotFound) {
|
|
||||||
failed++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Debug().
|
|
||||||
Stringer("room_id", portal.MXID).
|
|
||||||
Stringer("pn", pn).
|
|
||||||
Stringer("join_sender", memberEvt.Sender).
|
|
||||||
Time("join_ts", time.UnixMilli(memberEvt.Timestamp)).
|
|
||||||
Msg("Removed PN ghost from LID DM")
|
|
||||||
removed++
|
|
||||||
}
|
|
||||||
log.Info().
|
|
||||||
Int("removed_ghosts", removed).
|
|
||||||
Int("failed", failed).
|
|
||||||
Int("total_lid_portals", len(lidPortals)).
|
|
||||||
Msg("Finished removing rejoined PN ghosts from LID DMs")
|
|
||||||
if failed == 0 && ctx.Err() == nil {
|
|
||||||
wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dm_pn_ghosts_removed_v2", "true")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type lidPortal struct {
|
|
||||||
ID networkid.PortalID
|
|
||||||
MXID id.RoomID
|
|
||||||
}
|
|
||||||
|
|
||||||
var scanLIDPortal = dbutil.ConvertRowFn[lidPortal](func(row dbutil.Scannable) (portal lidPortal, err error) {
|
|
||||||
err = row.Scan(&portal.ID, &portal.MXID)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,6 @@ func (wl *WALogin) handleEvent(rawEvt any) {
|
||||||
wl.QRs[i] = strings.Replace(code, evt.OldSecret, evt.NewSecret, 1)
|
wl.QRs[i] = strings.Replace(code, evt.OldSecret, evt.NewSecret, 1)
|
||||||
}
|
}
|
||||||
wl.ADVRotate.Set()
|
wl.ADVRotate.Set()
|
||||||
return
|
|
||||||
case *events.QRScannedWithoutMultidevice:
|
case *events.QRScannedWithoutMultidevice:
|
||||||
wl.Log.Error().Msg("QR code scanned without multidevice enabled")
|
wl.Log.Error().Msg("QR code scanned without multidevice enabled")
|
||||||
wl.LoginError = ErrLoginMultideviceNotEnabled
|
wl.LoginError = ErrLoginMultideviceNotEnabled
|
||||||
|
|
@ -324,6 +323,7 @@ func (wl *WALogin) handleEvent(rawEvt any) {
|
||||||
wl.LoginError = ErrUnexpectedDisconnect
|
wl.LoginError = ErrUnexpectedDisconnect
|
||||||
case *events.ManualLoginReconnect:
|
case *events.ManualLoginReconnect:
|
||||||
wl.Received515.Set()
|
wl.Received515.Set()
|
||||||
|
// PairSuccess is dispatched separately and may arrive after the 515.
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
wl.Log.Warn().Type("event_type", evt).Msg("Got unexpected event")
|
wl.Log.Warn().Type("event_type", evt).Msg("Got unexpected event")
|
||||||
|
|
@ -373,14 +373,9 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
case <-wl.PasskeyRequest.GetChan():
|
case <-wl.PasskeyRequest.GetChan():
|
||||||
return wl.makePasskeyStep()
|
return wl.makePasskeyStep()
|
||||||
case <-wl.ADVRotate.GetChan():
|
case <-wl.ADVRotate.GetChan():
|
||||||
|
wl.Log.Debug().Msg("ADV secret was rotated, returning new QR immediately")
|
||||||
wl.ADVRotate.Clear()
|
wl.ADVRotate.Clear()
|
||||||
if currentIndex < 0 || currentIndex >= len(wl.QRs) {
|
return makeQRStep(wl.QRs[nextIndex]), nil
|
||||||
wl.Log.Debug().Msg("ADV secret was rotated, but no QRs are left to return")
|
|
||||||
wl.Cancel()
|
|
||||||
return nil, ErrLoginTimeout
|
|
||||||
}
|
|
||||||
wl.Log.Debug().Msg("ADV secret was rotated, returning current QR immediately")
|
|
||||||
return makeQRStep(wl.QRs[currentIndex]), nil
|
|
||||||
case <-wl.LoginComplete.GetChan():
|
case <-wl.LoginComplete.GetChan():
|
||||||
// continue
|
// continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -56,9 +57,9 @@ func (wa *WhatsAppClient) resyncWASARootSecrets(ctx context.Context) {
|
||||||
success := true
|
success := true
|
||||||
if err := wa.Client.FetchAppState(ctx, appstate.WAPatchRegularHigh, true, false); err != nil {
|
if err := wa.Client.FetchAppState(ctx, appstate.WAPatchRegularHigh, true, false); err != nil {
|
||||||
log.Err(err).Msg("Failed to resync WASA root secrets")
|
log.Err(err).Msg("Failed to resync WASA root secrets")
|
||||||
// Mark the sync as completed on errors too, because some get permanently stuck (mismatching LTHash,
|
// Mark the sync as completed on mismatching LTHash errors, because those can get permanently stuck
|
||||||
// expired patch blobs), and a failed full sync already requests app state recovery from the phone.
|
// (retrying won't help). In case it's not stuck, a single call should be enough to request a resync.
|
||||||
if ctx.Err() != nil {
|
if !errors.Is(err, appstate.ErrMismatchingLTHash) || ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
success = false
|
success = false
|
||||||
|
|
|
||||||
|
|
@ -54,38 +54,23 @@ func (wa *WhatsAppConnector) getProxy(ctx context.Context, reason string) (strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow.Client, isLogin bool) error {
|
func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow.Client, isLogin bool) error {
|
||||||
var proxy string
|
if wa.Config.ProxyOnlyLogin && !isLogin {
|
||||||
if !wa.Config.ProxyOnlyLogin || isLogin {
|
return nil
|
||||||
|
}
|
||||||
reason := "connect"
|
reason := "connect"
|
||||||
if isLogin {
|
if isLogin {
|
||||||
reason = "login"
|
reason = "login"
|
||||||
}
|
}
|
||||||
var err error
|
if proxy, err := wa.getProxy(ctx, reason); err != nil {
|
||||||
proxy, err = wa.getProxy(ctx, reason)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get proxy address: %w", err)
|
return fmt.Errorf("failed to get proxy address: %w", err)
|
||||||
}
|
} else if proxy == "" {
|
||||||
}
|
return nil
|
||||||
settings := wa.Bridge.GetHTTPClientSettings().WithGlobalTimeout(0)
|
} else if err = client.SetProxyAddress(proxy, whatsmeow.SetProxyOptions{
|
||||||
if settings.HTTPProxy == nil {
|
OnlyLogin: wa.Config.ProxyOnlyLogin,
|
||||||
settings.HTTPProxy = http.ProxyFromEnvironment
|
NoMedia: wa.Config.ProxyOnlyLogin,
|
||||||
}
|
}); err != nil {
|
||||||
proxySettings := settings
|
|
||||||
if proxy != "" {
|
|
||||||
var err error
|
|
||||||
proxySettings, err = settings.WithProxy(proxy)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set proxy address: %w", err)
|
return fmt.Errorf("failed to set proxy address: %w", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
client.SetPreLoginHTTPClient(proxySettings.Compile())
|
|
||||||
if wa.Config.ProxyOnlyLogin {
|
|
||||||
proxySettings = settings
|
|
||||||
}
|
|
||||||
client.SetWebsocketHTTPClient(proxySettings.Compile())
|
|
||||||
client.SetMediaHTTPClient(proxySettings.WithResponseHeaderTimeout(0).Compile())
|
|
||||||
if proxy != "" {
|
|
||||||
zerolog.Ctx(ctx).Debug().Msg("Enabled proxy")
|
zerolog.Ctx(ctx).Debug().Msg("Enabled proxy")
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,12 @@ 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) {
|
||||||
jid := waid.ParseUserID(ghost.ID)
|
|
||||||
if jid.Server == types.BotServer {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time
|
lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time
|
||||||
if ghost.AvatarID != "" && lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
if lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wa.resyncQueueLock.Lock()
|
wa.resyncQueueLock.Lock()
|
||||||
|
jid := waid.ParseUserID(ghost.ID)
|
||||||
if _, exists := wa.resyncQueue[jid]; !exists {
|
if _, exists := wa.resyncQueue[jid]; !exists {
|
||||||
wa.resyncQueue[jid] = resyncQueueItem{ghost: ghost}
|
wa.resyncQueue[jid] = resyncQueueItem{ghost: ghost}
|
||||||
nextResyncIn := time.Until(wa.nextResync).String()
|
nextResyncIn := time.Until(wa.nextResync).String()
|
||||||
|
|
@ -119,17 +116,14 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID
|
||||||
var ghostJIDs []types.JID
|
var ghostJIDs []types.JID
|
||||||
var ghosts []*bridgev2.Ghost
|
var ghosts []*bridgev2.Ghost
|
||||||
var portals []*bridgev2.Portal
|
var portals []*bridgev2.Portal
|
||||||
selectedGhosts := make(map[types.JID]*bridgev2.Ghost)
|
|
||||||
for jid, item := range queue {
|
for jid, item := range queue {
|
||||||
var lastSync time.Time
|
var lastSync time.Time
|
||||||
unknownAvatar := false
|
|
||||||
if item.ghost != nil {
|
if item.ghost != nil {
|
||||||
lastSync = item.ghost.Metadata.(*waid.GhostMetadata).LastSync.Time
|
lastSync = item.ghost.Metadata.(*waid.GhostMetadata).LastSync.Time
|
||||||
unknownAvatar = item.ghost.AvatarID == ""
|
|
||||||
} else if item.portal != nil {
|
} else if item.portal != nil {
|
||||||
lastSync = item.portal.Metadata.(*waid.PortalMetadata).LastSync.Time
|
lastSync = item.portal.Metadata.(*waid.PortalMetadata).LastSync.Time
|
||||||
}
|
}
|
||||||
if !unknownAvatar && lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
if lastSync.Add(ResyncMinInterval).After(time.Now()) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Stringer("jid", jid).
|
Stringer("jid", jid).
|
||||||
Time("last_sync", lastSync).
|
Time("last_sync", lastSync).
|
||||||
|
|
@ -137,23 +131,12 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if item.ghost != nil {
|
if item.ghost != nil {
|
||||||
selectedGhosts[jid] = item.ghost
|
ghosts = append(ghosts, item.ghost)
|
||||||
|
ghostJIDs = append(ghostJIDs, jid)
|
||||||
} else if item.portal != nil {
|
} else if item.portal != nil {
|
||||||
portals = append(portals, item.portal)
|
portals = append(portals, item.portal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for jid, ghost := range selectedGhosts {
|
|
||||||
if jid.Server == types.DefaultUserServer {
|
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Stringer("jid", jid).Msg("Failed to get LID for background sync")
|
|
||||||
} else if _, lidSelected := selectedGhosts[lid]; lidSelected {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ghosts = append(ghosts, ghost)
|
|
||||||
ghostJIDs = append(ghostJIDs, jid)
|
|
||||||
}
|
|
||||||
for _, portal := range portals {
|
for _, portal := range portals {
|
||||||
wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
|
|
@ -182,7 +165,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID
|
||||||
log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync")
|
log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, ghost.AvatarID == "" || (info.PictureID != "" && string(ghost.AvatarID) != info.PictureID))
|
userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync")
|
log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync")
|
||||||
continue
|
continue
|
||||||
|
|
@ -489,19 +472,16 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo {
|
func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo {
|
||||||
info := &bridgev2.UserInfo{
|
return &bridgev2.UserInfo{
|
||||||
Identifiers: ghost.Identifiers,
|
Identifiers: ghost.Identifiers,
|
||||||
Name: &ghost.Name,
|
Name: &ghost.Name,
|
||||||
|
Avatar: &bridgev2.Avatar{
|
||||||
|
ID: ghost.AvatarID,
|
||||||
|
Remove: ghost.AvatarID == "" || ghost.AvatarMXC == "",
|
||||||
|
MXC: ghost.AvatarMXC,
|
||||||
|
Hash: ghost.AvatarHash,
|
||||||
|
},
|
||||||
IsBot: &ghost.IsBot,
|
IsBot: &ghost.IsBot,
|
||||||
ExtraProfile: ghost.ExtraProfile,
|
ExtraProfile: ghost.ExtraProfile,
|
||||||
}
|
}
|
||||||
if ghost.AvatarID != "" && ghost.AvatarID != "unauthorized" {
|
|
||||||
info.Avatar = &bridgev2.Avatar{
|
|
||||||
ID: ghost.AvatarID,
|
|
||||||
Remove: ghost.AvatarMXC == "",
|
|
||||||
MXC: ghost.AvatarMXC,
|
|
||||||
Hash: ghost.AvatarHash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
-- v0 -> v11 (compatible with v3+): Latest revision
|
-- v0 -> v10 (compatible with v3+): Latest revision
|
||||||
|
|
||||||
CREATE TABLE whatsapp_poll_option_id (
|
CREATE TABLE whatsapp_poll_option_id (
|
||||||
bridge_id TEXT NOT NULL,
|
bridge_id TEXT NOT NULL,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue