diff --git a/commands.go b/commands.go index b399eab..a0c644a 100644 --- a/commands.go +++ b/commands.go @@ -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("✅") } diff --git a/config/bridge.go b/config/bridge.go index e463a3c..a91435d 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -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"` diff --git a/config/upgrade.go b/config/upgrade.go index 1a7ac62..cedf95c 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -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") diff --git a/database/disappearingmessage.go b/database/disappearingmessage.go index 003c792..d21c14b 100644 --- a/database/disappearingmessage.go +++ b/database/disappearingmessage.go @@ -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 } diff --git a/disappear.go b/disappear.go index 4a41a8a..ff48116 100644 --- a/disappear.go +++ b/disappear.go @@ -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) diff --git a/example-config.yaml b/example-config.yaml index a5b669d..4e2faaa 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -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 diff --git a/historysync.go b/historysync.go index 3503b8b..d1dd386 100644 --- a/historysync.go +++ b/historysync.go @@ -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)) diff --git a/portal.go b/portal.go index b74b01d..6ef7502 100644 --- a/portal.go +++ b/portal.go @@ -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 }