|
@@ -300,6 +300,8 @@ func getMessageType(waMsg *waProto.Message) string {
|
|
|
switch waMsg.GetProtocolMessage().GetType() {
|
|
|
case waProto.ProtocolMessage_REVOKE:
|
|
|
return "revoke"
|
|
|
+ case waProto.ProtocolMessage_EPHEMERAL_SETTING:
|
|
|
+ return "disappearing timer change"
|
|
|
case waProto.ProtocolMessage_APP_STATE_SYNC_KEY_SHARE, waProto.ProtocolMessage_HISTORY_SYNC_NOTIFICATION, waProto.ProtocolMessage_INITIAL_SECURITY_NOTIFICATION_SETTING_SYNC:
|
|
|
return "ignore"
|
|
|
default:
|
|
@@ -342,6 +344,52 @@ func getMessageType(waMsg *waProto.Message) string {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func pluralUnit(val int, name string) string {
|
|
|
+ if val == 1 {
|
|
|
+ return fmt.Sprintf("%d %s", val, name)
|
|
|
+ } else if val == 0 {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ return fmt.Sprintf("%d %ss", val, name)
|
|
|
+}
|
|
|
+
|
|
|
+func naturalJoin(parts []string) string {
|
|
|
+ if len(parts) == 0 {
|
|
|
+ return ""
|
|
|
+ } else if len(parts) == 1 {
|
|
|
+ return parts[0]
|
|
|
+ } else if len(parts) == 2 {
|
|
|
+ return fmt.Sprintf("%s and %s", parts[0], parts[1])
|
|
|
+ } else {
|
|
|
+ return fmt.Sprintf("%s and %s", strings.Join(parts[:len(parts)-1], ", "), parts[len(parts)-1])
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func formatDuration(d time.Duration) string {
|
|
|
+ const Day = time.Hour * 24
|
|
|
+
|
|
|
+ var days, hours, minutes, seconds int
|
|
|
+ days, d = int(d/Day), d%Day
|
|
|
+ hours, d = int(d/time.Hour), d%time.Hour
|
|
|
+ minutes, d = int(d/time.Minute), d%time.Minute
|
|
|
+ seconds = int(d / time.Second)
|
|
|
+
|
|
|
+ parts := make([]string, 0, 4)
|
|
|
+ if days > 0 {
|
|
|
+ parts = append(parts, pluralUnit(days, "day"))
|
|
|
+ }
|
|
|
+ if hours > 0 {
|
|
|
+ parts = append(parts, pluralUnit(hours, "hour"))
|
|
|
+ }
|
|
|
+ if minutes > 0 {
|
|
|
+ parts = append(parts, pluralUnit(seconds, "minute"))
|
|
|
+ }
|
|
|
+ if seconds > 0 {
|
|
|
+ parts = append(parts, pluralUnit(seconds, "second"))
|
|
|
+ }
|
|
|
+ return naturalJoin(parts)
|
|
|
+}
|
|
|
+
|
|
|
func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User, info *types.MessageInfo, waMsg *waProto.Message) *ConvertedMessage {
|
|
|
switch {
|
|
|
case waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil:
|
|
@@ -366,6 +414,23 @@ func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User,
|
|
|
return portal.convertLiveLocationMessage(intent, waMsg.GetLiveLocationMessage())
|
|
|
case waMsg.GroupInviteMessage != nil:
|
|
|
return portal.convertGroupInviteMessage(intent, info, waMsg.GetGroupInviteMessage())
|
|
|
+ case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waProto.ProtocolMessage_EPHEMERAL_SETTING:
|
|
|
+ portal.ExpirationTime = waMsg.ProtocolMessage.GetEphemeralExpiration()
|
|
|
+ portal.Update()
|
|
|
+ var msg string
|
|
|
+ if portal.ExpirationTime == 0 {
|
|
|
+ msg = "Turned off disappearing messages"
|
|
|
+ } else {
|
|
|
+ msg = fmt.Sprintf("Set the disappearing message timer to %s", formatDuration(time.Duration(portal.ExpirationTime)*time.Second))
|
|
|
+ }
|
|
|
+ return &ConvertedMessage{
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: &event.MessageEventContent{
|
|
|
+ Body: msg,
|
|
|
+ MsgType: event.MsgNotice,
|
|
|
+ },
|
|
|
+ }
|
|
|
default:
|
|
|
return nil
|
|
|
}
|
|
@@ -477,6 +542,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
|
|
}
|
|
|
var eventID id.EventID
|
|
|
if existingMsg != nil {
|
|
|
+ portal.MarkDisappearing(existingMsg.MXID, converted.ExpiresIn, false)
|
|
|
converted.Content.SetEdit(existingMsg.MXID)
|
|
|
} else if len(converted.ReplyTo) > 0 {
|
|
|
portal.SetReply(converted.Content, converted.ReplyTo)
|
|
@@ -485,6 +551,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
|
|
if err != nil {
|
|
|
portal.log.Errorfln("Failed to send %s to Matrix: %v", msgID, err)
|
|
|
} else {
|
|
|
+ portal.MarkDisappearing(resp.EventID, converted.ExpiresIn, false)
|
|
|
eventID = resp.EventID
|
|
|
}
|
|
|
// TODO figure out how to handle captions with undecryptable messages turning decryptable
|
|
@@ -493,6 +560,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(resp.EventID, converted.ExpiresIn, false)
|
|
|
eventID = resp.EventID
|
|
|
}
|
|
|
}
|
|
@@ -501,11 +569,13 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
|
|
resp, err = portal.sendMessage(converted.Intent, converted.Type, subEvt, nil, evt.Info.Timestamp.UnixMilli())
|
|
|
if err != nil {
|
|
|
portal.log.Errorfln("Failed to send sub-event %d of %s to Matrix: %v", index+1, msgID, err)
|
|
|
+ } else {
|
|
|
+ portal.MarkDisappearing(resp.EventID, converted.ExpiresIn, false)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if len(eventID) != 0 {
|
|
|
- portal.finishHandling(existingMsg, &evt.Info, resp.EventID, false)
|
|
|
+ portal.finishHandling(existingMsg, &evt.Info, eventID, false)
|
|
|
}
|
|
|
} else if msgType == "revoke" {
|
|
|
portal.HandleMessageRevoke(source, &evt.Info, evt.Message.GetProtocolMessage().GetKey())
|
|
@@ -1194,6 +1264,10 @@ func (portal *Portal) IsPrivateChat() bool {
|
|
|
return portal.Key.JID.Server == types.DefaultUserServer
|
|
|
}
|
|
|
|
|
|
+func (portal *Portal) IsGroupChat() bool {
|
|
|
+ return portal.Key.JID.Server == types.GroupServer
|
|
|
+}
|
|
|
+
|
|
|
func (portal *Portal) IsBroadcastList() bool {
|
|
|
return portal.Key.JID.Server == types.BroadcastServer
|
|
|
}
|
|
@@ -1337,7 +1411,8 @@ type ConvertedMessage struct {
|
|
|
|
|
|
MultiEvent []*event.MessageEventContent
|
|
|
|
|
|
- ReplyTo types.MessageID
|
|
|
+ ReplyTo types.MessageID
|
|
|
+ ExpiresIn uint32
|
|
|
}
|
|
|
|
|
|
func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, msg *waProto.Message) *ConvertedMessage {
|
|
@@ -1346,6 +1421,7 @@ func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, msg *waPr
|
|
|
MsgType: event.MsgText,
|
|
|
}
|
|
|
var replyTo types.MessageID
|
|
|
+ var expiresIn uint32
|
|
|
if msg.GetExtendedTextMessage() != nil {
|
|
|
content.Body = msg.GetExtendedTextMessage().GetText()
|
|
|
|
|
@@ -1354,9 +1430,16 @@ func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, msg *waPr
|
|
|
portal.bridge.Formatter.ParseWhatsApp(content, contextInfo.GetMentionedJid())
|
|
|
replyTo = contextInfo.GetStanzaId()
|
|
|
}
|
|
|
+ expiresIn = contextInfo.GetExpiration()
|
|
|
}
|
|
|
|
|
|
- return &ConvertedMessage{Intent: intent, Type: event.EventMessage, Content: content, ReplyTo: replyTo}
|
|
|
+ return &ConvertedMessage{
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: content,
|
|
|
+ ReplyTo: replyTo,
|
|
|
+ ExpiresIn: expiresIn,
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, msg *waProto.LiveLocationMessage) *ConvertedMessage {
|
|
@@ -1368,10 +1451,11 @@ func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, m
|
|
|
content.Body += ": " + msg.GetCaption()
|
|
|
}
|
|
|
return &ConvertedMessage{
|
|
|
- Intent: intent,
|
|
|
- Type: event.EventMessage,
|
|
|
- Content: content,
|
|
|
- ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: content,
|
|
|
+ ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1419,10 +1503,11 @@ func (portal *Portal) convertLocationMessage(intent *appservice.IntentAPI, msg *
|
|
|
}
|
|
|
|
|
|
return &ConvertedMessage{
|
|
|
- Intent: intent,
|
|
|
- Type: event.EventMessage,
|
|
|
- Content: content,
|
|
|
- ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: content,
|
|
|
+ ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1447,11 +1532,12 @@ func (portal *Portal) convertGroupInviteMessage(intent *appservice.IntentAPI, in
|
|
|
},
|
|
|
}
|
|
|
return &ConvertedMessage{
|
|
|
- Intent: intent,
|
|
|
- Type: event.EventMessage,
|
|
|
- Content: content,
|
|
|
- Extra: extraAttrs,
|
|
|
- ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: content,
|
|
|
+ Extra: extraAttrs,
|
|
|
+ ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1483,10 +1569,11 @@ func (portal *Portal) convertContactMessage(intent *appservice.IntentAPI, msg *w
|
|
|
}
|
|
|
|
|
|
return &ConvertedMessage{
|
|
|
- Intent: intent,
|
|
|
- Type: event.EventMessage,
|
|
|
- Content: content,
|
|
|
- ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ Intent: intent,
|
|
|
+ Type: event.EventMessage,
|
|
|
+ Content: content,
|
|
|
+ ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1510,6 +1597,7 @@ func (portal *Portal) convertContactsArrayMessage(intent *appservice.IntentAPI,
|
|
|
Body: fmt.Sprintf("Sent %s", name),
|
|
|
},
|
|
|
ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
MultiEvent: contacts,
|
|
|
}
|
|
|
}
|
|
@@ -1815,12 +1903,13 @@ func (portal *Portal) convertMediaMessage(intent *appservice.IntentAPI, source *
|
|
|
}
|
|
|
|
|
|
return &ConvertedMessage{
|
|
|
- Intent: intent,
|
|
|
- Type: eventType,
|
|
|
- Content: content,
|
|
|
- Caption: captionContent,
|
|
|
- ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
- Extra: extraContent,
|
|
|
+ Intent: intent,
|
|
|
+ Type: eventType,
|
|
|
+ Content: content,
|
|
|
+ Caption: captionContent,
|
|
|
+ ReplyTo: msg.GetContextInfo().GetStanzaId(),
|
|
|
+ ExpiresIn: msg.GetContextInfo().GetExpiration(),
|
|
|
+ Extra: extraContent,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -2047,6 +2136,9 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waP
|
|
|
ctxInfo.QuotedMessage = &waProto.Message{Conversation: proto.String("")}
|
|
|
}
|
|
|
}
|
|
|
+ if portal.ExpirationTime != 0 {
|
|
|
+ ctxInfo.Expiration = proto.Uint32(portal.ExpirationTime)
|
|
|
+ }
|
|
|
relaybotFormatted := false
|
|
|
if !sender.IsLoggedIn() || (portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User) {
|
|
|
if !portal.HasRelaybot() {
|
|
@@ -2075,7 +2167,7 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waP
|
|
|
if content.MsgType == event.MsgEmote && !relaybotFormatted {
|
|
|
text = "/me " + text
|
|
|
}
|
|
|
- if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil {
|
|
|
+ if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil || ctxInfo.Expiration != nil {
|
|
|
msg.ExtendedTextMessage = &waProto.ExtendedTextMessage{
|
|
|
Text: &text,
|
|
|
ContextInfo: &ctxInfo,
|
|
@@ -2227,6 +2319,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
|
|
|
if msg == nil {
|
|
|
return
|
|
|
}
|
|
|
+ portal.MarkDisappearing(evt.ID, portal.ExpirationTime, true)
|
|
|
info := portal.generateMessageInfo(sender)
|
|
|
dbMsg := portal.markHandled(nil, info, evt.ID, false, true, false)
|
|
|
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
|
|
@@ -2333,6 +2426,7 @@ func (portal *Portal) HandleMatrixReadReceipt(sender *User, eventID id.EventID,
|
|
|
portal.log.Warnfln("Failed to mark %v as read by %s: %v", ids, sender.JID, err)
|
|
|
}
|
|
|
}
|
|
|
+ portal.ScheduleDisappearing()
|
|
|
}
|
|
|
|
|
|
func typingDiff(prev, new []id.UserID) (started, stopped []id.UserID) {
|