mirror of
https://github.com/mautrix/whatsapp.git
synced 2025-03-14 14:15:38 +00:00
165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
// 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 (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"go.mau.fi/whatsmeow/types"
|
|
"maunium.net/go/mautrix/bridgev2"
|
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
|
|
|
"go.mau.fi/mautrix-whatsapp/pkg/connector/wadb"
|
|
"go.mau.fi/mautrix-whatsapp/pkg/msgconv"
|
|
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
|
)
|
|
|
|
func isFailedMedia(converted *bridgev2.ConvertedMessage) bool {
|
|
if len(converted.Parts) == 0 || converted.Parts[0].Extra == nil {
|
|
return false
|
|
}
|
|
_, ok := converted.Parts[0].Extra[msgconv.FailedMediaField].(*msgconv.PreparedMedia)
|
|
return ok
|
|
}
|
|
|
|
func (wa *WhatsAppClient) processFailedMedia(ctx context.Context, portalKey networkid.PortalKey, msgID networkid.MessageID, converted *bridgev2.ConvertedMessage, isBackfill bool) *wadb.MediaRequest {
|
|
if len(converted.Parts) == 0 || converted.Parts[0].Extra == nil {
|
|
return nil
|
|
}
|
|
field, ok := converted.Parts[0].Extra[msgconv.FailedMediaField].(*msgconv.PreparedMedia)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
req := &wadb.MediaRequest{
|
|
UserLoginID: wa.UserLogin.ID,
|
|
MessageID: msgID,
|
|
PortalKey: portalKey,
|
|
MediaKey: field.FailedKeys.Key,
|
|
Status: wadb.MediaBackfillRequestStatusNotRequested,
|
|
}
|
|
if isBackfill {
|
|
return req
|
|
}
|
|
err := wa.Main.DB.MediaRequest.Put(ctx, req)
|
|
if err != nil {
|
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to save failed media request")
|
|
}
|
|
if wa.Main.Config.HistorySync.MediaRequests.AutoRequestMedia && wa.Main.Config.HistorySync.MediaRequests.RequestMethod == MediaRequestMethodImmediate {
|
|
go wa.sendMediaRequest(context.WithoutCancel(ctx), req)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) {
|
|
log := wa.UserLogin.Log.With().Str("loop", "media requests").Logger()
|
|
ctx = log.WithContext(ctx)
|
|
tzName := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).Timezone
|
|
userTz, err := time.LoadLocation(tzName)
|
|
var startIn time.Duration
|
|
if tzName != "" && err == nil && userTz != nil {
|
|
now := time.Now()
|
|
startAt := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, userTz)
|
|
startAt = startAt.Add(time.Duration(wa.Main.Config.HistorySync.MediaRequests.RequestLocalTime) * time.Minute)
|
|
if startAt.Before(now) {
|
|
startAt = startAt.AddDate(0, 0, 1)
|
|
}
|
|
startIn = time.Until(startAt)
|
|
} else {
|
|
startIn = 8 * time.Hour
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(startIn):
|
|
}
|
|
ticker := time.NewTicker(24 * time.Hour)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
wa.sendMediaRequests(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (wa *WhatsAppClient) sendMediaRequests(ctx context.Context) {
|
|
reqs, err := wa.Main.DB.MediaRequest.GetUnrequestedForUserLogin(ctx, wa.UserLogin.ID)
|
|
if err != nil {
|
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get media requests from database")
|
|
return
|
|
} else if len(reqs) == 0 {
|
|
return
|
|
}
|
|
zerolog.Ctx(ctx).Info().Int("request_count", len(reqs)).Msg("Sending media requests")
|
|
for _, req := range reqs {
|
|
wa.sendMediaRequest(ctx, req)
|
|
}
|
|
}
|
|
|
|
func (wa *WhatsAppClient) sendMediaRequest(ctx context.Context, req *wadb.MediaRequest) {
|
|
log := zerolog.Ctx(ctx).With().Str("action", "send media request").Str("message_id", string(req.MessageID)).Logger()
|
|
defer func() {
|
|
err := wa.Main.DB.MediaRequest.Put(ctx, req)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to save media request status")
|
|
}
|
|
}()
|
|
msg, err := wa.Main.Bridge.DB.Message.GetPartByID(ctx, wa.UserLogin.ID, req.MessageID, "")
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to get media retry target message from database")
|
|
req.Status = wadb.MediaBackfillRequestStatusRequestSkipped
|
|
return
|
|
} else if msg == nil {
|
|
log.Warn().Msg("Media retry target message not found in database")
|
|
req.Status = wadb.MediaBackfillRequestStatusRequestSkipped
|
|
return
|
|
} else if msg.Metadata.(*waid.MessageMetadata).Error != waid.MsgErrMediaNotFound {
|
|
log.Debug().Msg("Not sending media retry for message that doesn't have media error")
|
|
req.Status = wadb.MediaBackfillRequestStatusRequestSkipped
|
|
return
|
|
}
|
|
err = wa.sendMediaRequestDirect(req.MessageID, req.MediaKey)
|
|
if err != nil {
|
|
log.Err(err).Msg("Failed to send media retry request")
|
|
req.Status = wadb.MediaBackfillRequestStatusRequestFailed
|
|
req.Error = err.Error()
|
|
} else {
|
|
log.Debug().Msg("Sent media retry request")
|
|
req.Status = wadb.MediaBackfillRequestStatusRequested
|
|
}
|
|
}
|
|
|
|
func (wa *WhatsAppClient) sendMediaRequestDirect(rawMsgID networkid.MessageID, key []byte) error {
|
|
msgID, err := waid.ParseMessageID(rawMsgID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse message ID: %w", err)
|
|
}
|
|
return wa.Client.SendMediaRetryReceipt(&types.MessageInfo{
|
|
ID: msgID.ID,
|
|
MessageSource: types.MessageSource{
|
|
IsFromMe: msgID.Sender.User == wa.JID.User,
|
|
IsGroup: msgID.Chat.Server != types.DefaultUserServer,
|
|
Sender: msgID.Sender,
|
|
Chat: msgID.Chat,
|
|
},
|
|
}, key)
|
|
}
|