diff --git a/config/bridge.go b/config/bridge.go index 82cdd16..987fc0b 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -36,6 +36,7 @@ type BridgeConfig struct { CommunityTemplate string `yaml:"community_template"` ConnectionTimeout int `yaml:"connection_timeout"` + FetchMessageOnTimeout bool `yaml:"fetch_message_on_timeout"` LoginQRRegenCount int `yaml:"login_qr_regen_count"` MaxConnectionAttempts int `yaml:"max_connection_attempts"` ConnectionRetryDelay int `yaml:"connection_retry_delay"` diff --git a/example-config.yaml b/example-config.yaml index dfe2c25..2587d95 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -73,6 +73,10 @@ bridge: # WhatsApp connection timeout in seconds. connection_timeout: 20 + # If WhatsApp doesn't respond within connection_timeout, should the bridge try to fetch the message + # to see if it was actually bridged? Use this if you have problems with sends timing out but actually + # succeeding. + fetch_message_on_timeout: false # Number of times to regenerate QR code when logging in. # The regenerated QR code is sent as an edit and essentially multiplies the login timeout (20 seconds) login_qr_regen_count: 2 diff --git a/portal.go b/portal.go index 0b4959a..abec98b 100644 --- a/portal.go +++ b/portal.go @@ -1263,17 +1263,11 @@ func (portal *Portal) addRelaybotFormat(sender *User, content *event.MessageEven return true } -func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { - if !portal.HasRelaybot() && ( - (portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) || - portal.sendMatrixConnectionError(sender, evt.ID)) { - return - } +func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) *waProto.WebMessageInfo { content := evt.Content.AsMessage() if content == nil { - return + return nil } - portal.log.Debugfln("Received event %s", evt.ID) ts := uint64(evt.Timestamp / 1000) status := waProto.WebMessageInfo_ERROR @@ -1306,7 +1300,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway") } else { portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") - return + return nil } } else { relaybotFormatted = portal.addRelaybotFormat(sender, content) @@ -1316,7 +1310,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { if evt.Type == event.EventSticker { content.MsgType = event.MsgImage } - var err error + switch content.MsgType { case event.MsgText, event.MsgEmote, event.MsgNotice: text := content.Body @@ -1341,7 +1335,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { case event.MsgImage: media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaImage) if media == nil { - return + return nil } info.Message.ImageMessage = &waProto.ImageMessage{ Caption: &media.Caption, @@ -1356,7 +1350,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { case event.MsgVideo: media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaVideo) if media == nil { - return + return nil } duration := uint32(content.GetInfo().Duration) info.Message.VideoMessage = &waProto.VideoMessage{ @@ -1373,7 +1367,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { case event.MsgAudio: media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaAudio) if media == nil { - return + return nil } duration := uint32(content.GetInfo().Duration) info.Message.AudioMessage = &waProto.AudioMessage{ @@ -1388,7 +1382,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { case event.MsgFile: media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaDocument) if media == nil { - return + return nil } info.Message.DocumentMessage = &waProto.DocumentMessage{ Url: &media.URL, @@ -1401,44 +1395,73 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { } default: portal.log.Debugln("Unhandled Matrix event:", evt) + return nil + } + return info +} + +func (portal *Portal) wasMessageSent(sender *User, id string) bool { + _, err := sender.Conn.LoadMessagesAfter(portal.Key.JID, id, true, 0) + if err != nil { + if err != whatsapp.ErrServerRespondedWith404 { + portal.log.Warnfln("Failed to check if message was bridged without response: %v", err) + } + return false + } + return true +} + +func (portal *Portal) sendErrorMessage(sendErr error) id.EventID { + resp, err := portal.sendMainIntentMessage(event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: fmt.Sprintf("\u26a0 Your message may not have been bridged: %v", sendErr), + }) + if err != nil { + portal.log.Warnfln("Failed to send bridging error message:", err) + return "" + } + return resp.EventID +} + +var timeout = errors.New("message sending timed out") + +func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { + if !portal.HasRelaybot() && ( + (portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) || + portal.sendMatrixConnectionError(sender, evt.ID)) { return } + portal.log.Debugfln("Received event %s", evt.ID) + info := portal.convertMatrixMessage(sender, evt) portal.markHandled(sender, info, evt.ID) portal.log.Debugln("Sending event", evt.ID, "to WhatsApp") errChan := make(chan error, 1) go sender.Conn.SendRaw(info, errChan) - var errorSendResp *mautrix.RespSendEvent + var err error + var errorEventID id.EventID select { case err = <-errChan: case <-time.After(time.Duration(portal.bridge.Config.Bridge.ConnectionTimeout) * time.Second): - portal.log.Warnfln("Response when bridging Matrix event %s is taking long to arrive", evt.ID) - errorSendResp, err = portal.sendMainIntentMessage(event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: fmt.Sprintf("\u26a0 Your message may not have been bridged: message sending timed out"), - }) - if err != nil { - portal.log.Warnfln("Failed to send bridging timeout message:", err) + if portal.bridge.Config.Bridge.FetchMessageOnTimeout && portal.wasMessageSent(sender, info.Key.GetId()) { + portal.log.Debugln("Matrix event %s was bridged, but response didn't arrive within timeout") + } else { + portal.log.Warnfln("Response when bridging Matrix event %s is taking long to arrive", evt.ID) + errorEventID = portal.sendErrorMessage(timeout) } err = <-errChan } if err != nil { portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err) - _, err = portal.sendMainIntentMessage(event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: fmt.Sprintf("\u26a0 Your message may not have been bridged: %v", err), - }) - if err != nil { - portal.log.Warnfln("Failed to send bridging failure message:", err) - } + portal.sendErrorMessage(err) } else { - portal.log.Debugln("Handled Matrix event %s", evt.ID) + portal.log.Debugfln("Handled Matrix event %s", evt.ID) } - if errorSendResp != nil { - _, err = portal.MainIntent().RedactEvent(portal.MXID, errorSendResp.EventID) + if errorEventID != "" { + _, err = portal.MainIntent().RedactEvent(portal.MXID, errorEventID) if err != nil { - portal.log.Warnfln("Failed to redact timeout warning message %s: %v", errorSendResp.EventID, err) + portal.log.Warnfln("Failed to redact timeout warning message %s: %v", errorEventID, err) } } }