Fix disappearing message timing

This commit is contained in:
Tulir Asokan 2023-01-16 17:15:04 +02:00
parent ac8a2f8368
commit 3a5ca36d49
8 changed files with 57 additions and 70 deletions

View file

@ -1197,9 +1197,5 @@ func fnDisappearingTimer(ce *WrappedCommandEvent) {
return
}
ce.Portal.Update(nil)
if !ce.Portal.IsPrivateChat() && !ce.Bridge.Config.Bridge.DisappearingMessagesInGroups {
ce.Reply("Disappearing timer changed successfully, but this bridge is not configured to disappear messages in group chats.")
} else {
ce.React("✅")
}
ce.React("✅")
}

View file

@ -127,8 +127,7 @@ type BridgeConfig struct {
Deadline time.Duration `yaml:"-"`
} `yaml:"message_handling_timeout"`
DisableStatusBroadcastSend bool `yaml:"disable_status_broadcast_send"`
DisappearingMessagesInGroups bool `yaml:"disappearing_messages_in_groups"`
DisableStatusBroadcastSend bool `yaml:"disable_status_broadcast_send"`
DisableBridgeAlerts bool `yaml:"disable_bridge_alerts"`
CrashOnStreamReplaced bool `yaml:"crash_on_stream_replaced"`

View file

@ -92,7 +92,6 @@ func DoUpgrade(helper *up.Helper) {
helper.Copy(up.Bool, "bridge", "allow_user_invite")
helper.Copy(up.Str, "bridge", "command_prefix")
helper.Copy(up.Bool, "bridge", "federate_rooms")
helper.Copy(up.Bool, "bridge", "disappearing_messages_in_groups")
helper.Copy(up.Bool, "bridge", "disable_bridge_alerts")
helper.Copy(up.Bool, "bridge", "crash_on_stream_replaced")
helper.Copy(up.Bool, "bridge", "url_previews")

View file

@ -39,16 +39,14 @@ func (dmq *DisappearingMessageQuery) New() *DisappearingMessage {
}
}
func (dmq *DisappearingMessageQuery) NewWithValues(roomID id.RoomID, eventID id.EventID, expireIn time.Duration, startNow bool) *DisappearingMessage {
func (dmq *DisappearingMessageQuery) NewWithValues(roomID id.RoomID, eventID id.EventID, expireIn time.Duration, expireAt time.Time) *DisappearingMessage {
dm := &DisappearingMessage{
db: dmq.db,
log: dmq.log,
RoomID: roomID,
EventID: eventID,
ExpireIn: expireIn,
}
if startNow {
dm.ExpireAt = time.Now().Add(dm.ExpireIn)
ExpireAt: expireAt,
}
return dm
}

View file

@ -27,22 +27,20 @@ import (
"maunium.net/go/mautrix-whatsapp/database"
)
func (portal *Portal) MarkDisappearing(txn dbutil.Execable, eventID id.EventID, expiresIn uint32, startNow bool) {
if expiresIn == 0 || (!portal.bridge.Config.Bridge.DisappearingMessagesInGroups && portal.IsGroupChat()) {
func (portal *Portal) MarkDisappearing(txn dbutil.Execable, eventID id.EventID, expiresIn time.Duration, startsAt time.Time) {
if expiresIn == 0 {
return
}
expiresAt := startsAt.Add(expiresIn)
msg := portal.bridge.DB.DisappearingMessage.NewWithValues(portal.MXID, eventID, time.Duration(expiresIn)*time.Second, startNow)
msg := portal.bridge.DB.DisappearingMessage.NewWithValues(portal.MXID, eventID, expiresIn, expiresAt)
msg.Insert(txn)
if startNow {
if expiresAt.Before(time.Now().Add(1 * time.Hour)) {
go portal.sleepAndDelete(msg)
}
}
func (portal *Portal) ScheduleDisappearing() {
if !portal.bridge.Config.Bridge.DisappearingMessagesInGroups && portal.IsGroupChat() {
return
}
nowPlusHour := time.Now().Add(1 * time.Hour)
for _, msg := range portal.bridge.DB.DisappearingMessage.StartAllUnscheduledInRoom(portal.MXID) {
if msg.ExpireAt.Before(nowPlusHour) {
@ -63,6 +61,11 @@ func (br *WABridge) SleepAndDeleteUpcoming() {
}
func (portal *Portal) sleepAndDelete(msg *database.DisappearingMessage) {
if _, alreadySleeping := portal.currentlySleepingToDelete.LoadOrStore(msg.EventID, true); alreadySleeping {
return
}
defer portal.currentlySleepingToDelete.Delete(msg.EventID)
sleepTime := msg.ExpireAt.Sub(time.Now())
portal.log.Debugfln("Sleeping for %s to make %s disappear", sleepTime, msg.EventID)
time.Sleep(sleepTime)

View file

@ -292,10 +292,6 @@ bridge:
# Whether or not created rooms should have federation enabled.
# If false, created portal rooms will never be federated.
federate_rooms: true
# Whether to enable disappearing messages in groups. If enabled, then the expiration time of
# the messages will be determined by the first user to read the message, rather than individually.
# If the bridge only has a single user, this can be turned on safely.
disappearing_messages_in_groups: false
# Should the bridge never send alerts to the bridge management room?
# These are mostly things like the user being logged out.
disable_bridge_alerts: false

View file

@ -49,8 +49,8 @@ type wrappedInfo struct {
MediaKey []byte
ExpirationStart uint64
ExpiresIn uint32
ExpirationStart time.Time
ExpiresIn time.Duration
}
func (user *User) handleHistorySyncsLoop() {
@ -666,7 +666,10 @@ func (portal *Portal) appendBatchEvents(source *User, converted *ConvertedMessag
if err != nil {
return err
}
expirationStart := raw.GetEphemeralStartTimestamp()
expirationStart := info.Timestamp
if raw.GetEphemeralStartTimestamp() > 0 {
expirationStart = time.Unix(int64(raw.GetEphemeralStartTimestamp()), 0)
}
mainInfo := &wrappedInfo{
MessageInfo: info,
Type: database.MsgNormal,
@ -799,14 +802,7 @@ func (portal *Portal) finishBatch(txn dbutil.Transaction, eventIDs []id.EventID,
}
if info.ExpiresIn > 0 {
if info.ExpirationStart > 0 {
remainingSeconds := time.Unix(int64(info.ExpirationStart), 0).Add(time.Duration(info.ExpiresIn) * time.Second).Sub(time.Now()).Seconds()
portal.log.Debugfln("Disappearing history sync message: expires in %d, started at %d, remaining %d", info.ExpiresIn, info.ExpirationStart, int(remainingSeconds))
portal.MarkDisappearing(txn, eventID, uint32(remainingSeconds), true)
} else {
portal.log.Debugfln("Disappearing history sync message: expires in %d (not started)", info.ExpiresIn)
portal.MarkDisappearing(txn, eventID, info.ExpiresIn, false)
}
portal.MarkDisappearing(txn, eventID, info.ExpiresIn, info.ExpirationStart)
}
}
portal.log.Infofln("Successfully sent %d events", len(eventIDs))

View file

@ -268,6 +268,8 @@ type Portal struct {
mediaErrorCache map[types.MessageID]*FailedMediaMeta
currentlySleepingToDelete sync.Map
relayUser *User
parentPortal *Portal
}
@ -613,13 +615,8 @@ func (portal *Portal) UpdateGroupDisappearingMessages(sender *types.JID, timesta
func (portal *Portal) formatDisappearingMessageNotice() string {
if portal.ExpirationTime == 0 {
return "Turned off disappearing messages"
} else {
msg := fmt.Sprintf("Set the disappearing message timer to %s", formatDuration(time.Duration(portal.ExpirationTime)*time.Second))
if !portal.bridge.Config.Bridge.DisappearingMessagesInGroups && portal.IsGroupChat() {
msg += ". However, this bridge is not configured to disappear messages in group chats."
}
return msg
}
return fmt.Sprintf("Set the disappearing message timer to %s", formatDuration(time.Duration(portal.ExpirationTime)*time.Second))
}
const UndecryptableMessageNotice = "Decrypting message from WhatsApp failed, waiting for sender to re-send... " +
@ -758,7 +755,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
var eventID id.EventID
var lastEventID id.EventID
if existingMsg != nil {
portal.MarkDisappearing(nil, existingMsg.MXID, converted.ExpiresIn, false)
portal.MarkDisappearing(nil, existingMsg.MXID, converted.ExpiresIn, evt.Info.Timestamp)
converted.Content.SetEdit(existingMsg.MXID)
} else if converted.ReplyTo != nil {
portal.SetReply(converted.Content, converted.ReplyTo, false)
@ -773,7 +770,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
portal.log.Errorfln("Failed to send %s to Matrix: %v", msgID, err)
} else {
if editTargetMsg == nil {
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, false)
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, evt.Info.Timestamp)
}
eventID = resp.EventID
lastEventID = eventID
@ -784,7 +781,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
if err != nil {
portal.log.Errorfln("Failed to send caption of %s to Matrix: %v", msgID, err)
} else {
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, false)
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, evt.Info.Timestamp)
lastEventID = resp.EventID
}
}
@ -794,7 +791,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
if err != nil {
portal.log.Errorfln("Failed to send sub-event %d of %s to Matrix: %v", index+1, msgID, err)
} else {
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, false)
portal.MarkDisappearing(nil, resp.EventID, converted.ExpiresIn, evt.Info.Timestamp)
lastEventID = resp.EventID
}
}
@ -2049,7 +2046,7 @@ type ConvertedMessage struct {
MultiEvent []*event.MessageEventContent
ReplyTo *ReplyInfo
ExpiresIn uint32
ExpiresIn time.Duration
Error database.MessageErrorType
MediaKey []byte
}
@ -2082,7 +2079,7 @@ func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, source *U
contextInfo := msg.GetExtendedTextMessage().GetContextInfo()
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, content, contextInfo.GetMentionedJid(), false, false)
expiresIn := contextInfo.GetExpiration()
expiresIn := time.Duration(contextInfo.GetExpiration()) * time.Second
extraAttrs := map[string]interface{}{}
extraAttrs["com.beeper.linkpreviews"] = portal.convertURLPreviewToBeeper(intent, source, msg.GetExtendedTextMessage())
@ -2105,7 +2102,7 @@ func (portal *Portal) convertTemplateMessage(intent *appservice.IntentAPI, sourc
MsgType: event.MsgText,
},
ReplyTo: GetReply(tplMsg.GetContextInfo()),
ExpiresIn: tplMsg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(tplMsg.GetContextInfo().GetExpiration()) * time.Second,
}
tpl := tplMsg.GetHydratedTemplate()
@ -2182,7 +2179,7 @@ func (portal *Portal) convertTemplateButtonReplyMessage(intent *appservice.Inten
},
},
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2195,7 +2192,7 @@ func (portal *Portal) convertListMessage(intent *appservice.IntentAPI, source *U
MsgType: event.MsgText,
},
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
body := msg.GetDescription()
if msg.GetTitle() != "" {
@ -2262,7 +2259,7 @@ func (portal *Portal) convertListResponseMessage(intent *appservice.IntentAPI, m
},
},
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2405,7 +2402,7 @@ func (portal *Portal) convertPollCreationMessage(intent *appservice.IntentAPI, m
},
},
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2422,7 +2419,7 @@ func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, m
Type: event.EventMessage,
Content: content,
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2474,7 +2471,7 @@ func (portal *Portal) convertLocationMessage(intent *appservice.IntentAPI, msg *
Type: event.EventMessage,
Content: content,
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2516,7 +2513,7 @@ func (portal *Portal) convertGroupInviteMessage(intent *appservice.IntentAPI, in
Content: content,
Extra: extraAttrs,
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2552,7 +2549,7 @@ func (portal *Portal) convertContactMessage(intent *appservice.IntentAPI, msg *w
Type: event.EventMessage,
Content: content,
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
}
}
@ -2576,7 +2573,7 @@ func (portal *Portal) convertContactsArrayMessage(intent *appservice.IntentAPI,
Body: fmt.Sprintf("Sent %s", name),
},
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
MultiEvent: contacts,
}
}
@ -2935,7 +2932,7 @@ func (portal *Portal) convertMediaMessageContent(intent *appservice.IntentAPI, m
Content: content,
Caption: captionContent,
ReplyTo: GetReply(msg.GetContextInfo()),
ExpiresIn: msg.GetContextInfo().GetExpiration(),
ExpiresIn: time.Duration(msg.GetContextInfo().GetExpiration()) * time.Second,
Extra: extraContent,
}
}
@ -3725,6 +3722,7 @@ func (portal *Portal) generateContextInfo(relatesTo *event.RelatesTo) *waProto.C
type extraConvertMeta struct {
PollOptions map[[32]byte]string
EditRootMsg *database.Message
}
func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, evt *event.Event) (*waProto.Message, *User, *extraConvertMeta, error) {
@ -3737,12 +3735,14 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
if !ok {
return nil, sender, nil, fmt.Errorf("%w %T", errUnexpectedParsedContentType, evt.Content.Parsed)
}
extraMeta := &extraConvertMeta{}
var editRootMsg *database.Message
if editEventID := content.RelatesTo.GetReplaceID(); editEventID != "" && portal.bridge.Config.Bridge.SendWhatsAppEdits {
editRootMsg = portal.bridge.DB.Message.GetByMXID(editEventID)
if editRootMsg == nil || editRootMsg.Type != database.MsgNormal || editRootMsg.IsFakeJID() || editRootMsg.Sender.User != sender.JID.User {
return nil, sender, nil, fmt.Errorf("edit rejected") // TODO more specific error message
return nil, sender, extraMeta, fmt.Errorf("edit rejected") // TODO more specific error message
}
extraMeta.EditRootMsg = editRootMsg
if content.NewContent != nil {
content = content.NewContent
}
@ -3753,7 +3753,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
relaybotFormatted := false
if !sender.IsLoggedIn() || (portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User) {
if !portal.HasRelaybot() {
return nil, sender, nil, errUserNotLoggedIn
return nil, sender, extraMeta, errUserNotLoggedIn
}
relaybotFormatted = portal.addRelaybotFormat(sender, content)
sender = portal.GetRelayUser()
@ -3774,7 +3774,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MsgText, event.MsgEmote, event.MsgNotice:
text := content.Body
if content.MsgType == event.MsgNotice && !portal.bridge.Config.Bridge.BridgeNotices {
return nil, sender, nil, errMNoticeDisabled
return nil, sender, extraMeta, errMNoticeDisabled
}
if content.Format == event.FormatHTML {
text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
@ -3788,7 +3788,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
}
hasPreview := portal.convertURLPreviewToWhatsApp(ctx, sender, evt, msg.ExtendedTextMessage)
if ctx.Err() != nil {
return nil, nil, nil, ctx.Err()
return nil, sender, extraMeta, ctx.Err()
}
if ctxInfo.StanzaId == nil && ctxInfo.MentionedJid == nil && ctxInfo.Expiration == nil && !hasPreview {
// No need for extended message
@ -3798,7 +3798,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MsgImage:
media, err := portal.preprocessMatrixMedia(ctx, sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaImage)
if media == nil {
return nil, sender, nil, err
return nil, sender, extraMeta, err
}
ctxInfo.MentionedJid = media.MentionedJIDs
msg.ImageMessage = &waProto.ImageMessage{
@ -3815,7 +3815,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MessageType(event.EventSticker.Type):
media, err := portal.preprocessMatrixMedia(ctx, sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaImage)
if media == nil {
return nil, sender, nil, err
return nil, sender, extraMeta, err
}
ctxInfo.MentionedJid = media.MentionedJIDs
msg.StickerMessage = &waProto.StickerMessage{
@ -3832,7 +3832,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
gifPlayback := content.GetInfo().MimeType == "image/gif"
media, err := portal.preprocessMatrixMedia(ctx, sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaVideo)
if media == nil {
return nil, sender, nil, err
return nil, sender, extraMeta, err
}
duration := uint32(content.GetInfo().Duration / 1000)
ctxInfo.MentionedJid = media.MentionedJIDs
@ -3852,7 +3852,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MsgAudio:
media, err := portal.preprocessMatrixMedia(ctx, sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaAudio)
if media == nil {
return nil, sender, nil, err
return nil, sender, extraMeta, err
}
duration := uint32(content.GetInfo().Duration / 1000)
msg.AudioMessage = &waProto.AudioMessage{
@ -3875,7 +3875,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MsgFile:
media, err := portal.preprocessMatrixMedia(ctx, sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaDocument)
if media == nil {
return nil, sender, nil, err
return nil, sender, extraMeta, err
}
msg.DocumentMessage = &waProto.DocumentMessage{
ContextInfo: ctxInfo,
@ -3901,7 +3901,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
case event.MsgLocation:
lat, long, err := parseGeoURI(content.GeoURI)
if err != nil {
return nil, sender, nil, fmt.Errorf("%w: %v", errInvalidGeoURI, err)
return nil, sender, extraMeta, fmt.Errorf("%w: %v", errInvalidGeoURI, err)
}
msg.LocationMessage = &waProto.LocationMessage{
DegreesLatitude: &lat,
@ -3910,7 +3910,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
ContextInfo: ctxInfo,
}
default:
return nil, sender, nil, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType)
return nil, sender, extraMeta, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType)
}
if editRootMsg != nil {
@ -3932,7 +3932,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
}
}
return msg, sender, nil, nil
return msg, sender, extraMeta, nil
}
func (portal *Portal) generateMessageInfo(sender *User) *types.MessageInfo {
@ -4022,7 +4022,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timing
if msg.PollCreationMessage != nil || msg.PollCreationMessageV2 != nil {
dbMsgType = database.MsgMatrixPoll
} else if msg.EditedMessage == nil {
portal.MarkDisappearing(nil, origEvtID, portal.ExpirationTime, true)
portal.MarkDisappearing(nil, origEvtID, time.Duration(portal.ExpirationTime)*time.Second, time.Now())
} else {
dbMsgType = database.MsgEdit
}