Move most double puppet source key adding to mautrix-go

This commit is contained in:
Tulir Asokan 2022-06-30 20:56:25 +03:00
parent e9f01b81d5
commit bf4c01648f
6 changed files with 51 additions and 120 deletions

2
go.mod
View file

@ -15,7 +15,7 @@ require (
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
maunium.net/go/maulogger/v2 v2.3.2 maunium.net/go/maulogger/v2 v2.3.2
maunium.net/go/mautrix v0.11.1-0.20220630105134-e8349f977b35 maunium.net/go/mautrix v0.11.1-0.20220630174618-e98784f2fe26
) )
require ( require (

4
go.sum
View file

@ -108,5 +108,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0= maunium.net/go/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.11.1-0.20220630105134-e8349f977b35 h1:aPiY7xtcRsP7o7u0TvkKY7JiyjVa0bUnsqCEMJQ3vU4= maunium.net/go/mautrix v0.11.1-0.20220630174618-e98784f2fe26 h1:wkfsp2ozyAQ9Vr9oAXbS9caWLhIffQ/Lxa04t7iUY54=
maunium.net/go/mautrix v0.11.1-0.20220630105134-e8349f977b35/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8= maunium.net/go/mautrix v0.11.1-0.20220630174618-e98784f2fe26/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8=

View file

@ -656,33 +656,16 @@ func (portal *Portal) appendBatchEvents(converted *ConvertedMessage, info *types
return nil return nil
} }
const backfillIDField = "fi.mau.whatsapp.backfill_msg_id"
func (portal *Portal) wrapBatchEvent(info *types.MessageInfo, intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}) (*event.Event, error) { func (portal *Portal) wrapBatchEvent(info *types.MessageInfo, intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}) (*event.Event, error) {
if extraContent == nil {
extraContent = map[string]interface{}{}
}
extraContent[backfillIDField] = info.ID
if intent.IsCustomPuppet {
extraContent[doublePuppetKey] = doublePuppetValue
}
wrappedContent := event.Content{ wrappedContent := event.Content{
Parsed: content, Parsed: content,
Raw: extraContent, Raw: extraContent,
} }
newEventType, err := portal.encrypt(&wrappedContent, eventType) newEventType, err := portal.encrypt(intent, &wrappedContent, eventType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if newEventType == event.EventEncrypted {
// Clear other custom keys if the event was encrypted, but keep the double puppet identifier
wrappedContent.Raw = map[string]interface{}{backfillIDField: info.ID}
if intent.IsCustomPuppet {
wrappedContent.Raw[doublePuppetKey] = doublePuppetValue
}
}
return &event.Event{ return &event.Event{
Sender: intent.UserID, Sender: intent.UserID,
Type: newEventType, Type: newEventType,

113
portal.go
View file

@ -331,7 +331,7 @@ func (portal *Portal) handleReceipt(receipt *events.Receipt, source *User) {
} }
intent := portal.bridge.GetPuppetByJID(receipt.Sender).IntentFor(portal) intent := portal.bridge.GetPuppetByJID(receipt.Sender).IntentFor(portal)
for _, msg := range markAsRead { for _, msg := range markAsRead {
err := intent.SetReadMarkers(portal.MXID, makeReadMarkerContent(msg.MXID, intent.IsCustomPuppet)) err := intent.SetReadMarkers(portal.MXID, source.makeReadMarkerContent(msg.MXID, intent.IsCustomPuppet))
if err != nil { if err != nil {
portal.log.Warnfln("Failed to mark message %s as read by %s: %v", msg.MXID, intent.UserID, err) portal.log.Warnfln("Failed to mark message %s as read by %s: %v", msg.MXID, intent.UserID, err)
} else { } else {
@ -740,7 +740,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
if source.MXID == intent.UserID { if source.MXID == intent.UserID {
// There are some edge cases (like call notices) where previous messages aren't marked as read // There are some edge cases (like call notices) where previous messages aren't marked as read
// when the user sends a message from another device, so just mark the new message as read to be safe. // when the user sends a message from another device, so just mark the new message as read to be safe.
err = intent.SetReadMarkers(portal.MXID, makeReadMarkerContent(lastEventID, true)) err = intent.SetReadMarkers(portal.MXID, source.makeReadMarkerContent(lastEventID, true))
if err != nil { if err != nil {
portal.log.Warnfln("Failed to mark own message %s as read by %s: %v", lastEventID, source.MXID, err) portal.log.Warnfln("Failed to mark own message %s as read by %s: %v", lastEventID, source.MXID, err)
} }
@ -1588,11 +1588,6 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, replyToID typ
return true return true
} }
type sendReactionContent struct {
event.ReactionEventContent
DoublePuppet string `json:"fi.mau.double_puppet_source,omitempty"`
}
func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *User, info *types.MessageInfo, reaction *waProto.ReactionMessage, existingMsg *database.Message) { func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *User, info *types.MessageInfo, reaction *waProto.ReactionMessage, existingMsg *database.Message) {
if existingMsg != nil { if existingMsg != nil {
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{ _, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
@ -1608,11 +1603,7 @@ func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *
return return
} }
extra := make(map[string]interface{}) resp, err := intent.RedactEvent(portal.MXID, existing.MXID)
if intent.IsCustomPuppet {
extra[doublePuppetKey] = doublePuppetValue
}
resp, err := intent.RedactEvent(portal.MXID, existing.MXID, mautrix.ReqRedact{Extra: extra})
if err != nil { if err != nil {
portal.log.Errorfln("Failed to redact reaction %s/%s from %s to %s: %v", existing.MXID, existing.JID, info.Sender, targetJID, err) portal.log.Errorfln("Failed to redact reaction %s/%s from %s to %s: %v", existing.MXID, existing.JID, info.Sender, targetJID, err)
} }
@ -1625,15 +1616,12 @@ func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *
return return
} }
var content sendReactionContent var content event.ReactionEventContent
content.RelatesTo = event.RelatesTo{ content.RelatesTo = event.RelatesTo{
Type: event.RelAnnotation, Type: event.RelAnnotation,
EventID: target.MXID, EventID: target.MXID,
Key: variationselector.Add(reaction.GetText()), Key: variationselector.Add(reaction.GetText()),
} }
if intent.IsCustomPuppet {
content.DoublePuppet = doublePuppetValue
}
resp, err := intent.SendMassagedMessageEvent(portal.MXID, event.EventReaction, &content, info.Timestamp.UnixMilli()) resp, err := intent.SendMassagedMessageEvent(portal.MXID, event.EventReaction, &content, info.Timestamp.UnixMilli())
if err != nil { if err != nil {
portal.log.Errorfln("Failed to bridge reaction %s from %s to %s: %v", info.ID, info.Sender, target.JID, err) portal.log.Errorfln("Failed to bridge reaction %s from %s to %s: %v", info.ID, info.Sender, target.JID, err)
@ -1651,14 +1639,10 @@ func (portal *Portal) HandleMessageRevoke(user *User, info *types.MessageInfo, k
return false return false
} }
intent := portal.bridge.GetPuppetByJID(info.Sender).IntentFor(portal) intent := portal.bridge.GetPuppetByJID(info.Sender).IntentFor(portal)
redactionReq := mautrix.ReqRedact{Extra: map[string]interface{}{}} _, err := intent.RedactEvent(portal.MXID, msg.MXID)
if intent.IsCustomPuppet {
redactionReq.Extra[doublePuppetKey] = doublePuppetValue
}
_, err := intent.RedactEvent(portal.MXID, msg.MXID, redactionReq)
if err != nil { if err != nil {
if errors.Is(err, mautrix.MForbidden) { if errors.Is(err, mautrix.MForbidden) {
_, err = portal.MainIntent().RedactEvent(portal.MXID, msg.MXID, redactionReq) _, err = portal.MainIntent().RedactEvent(portal.MXID, msg.MXID)
if err != nil { if err != nil {
portal.log.Errorln("Failed to redact %s: %v", msg.JID, err) portal.log.Errorln("Failed to redact %s: %v", msg.JID, err)
} }
@ -1673,49 +1657,29 @@ func (portal *Portal) sendMainIntentMessage(content *event.MessageEventContent)
return portal.sendMessage(portal.MainIntent(), event.EventMessage, content, nil, 0) return portal.sendMessage(portal.MainIntent(), event.EventMessage, content, nil, 0)
} }
func (portal *Portal) encrypt(content *event.Content, eventType event.Type) (event.Type, error) { func (portal *Portal) encrypt(intent *appservice.IntentAPI, content *event.Content, eventType event.Type) (event.Type, error) {
if portal.Encrypted && portal.bridge.Crypto != nil { if !portal.Encrypted || portal.bridge.Crypto == nil {
// TODO maybe the locking should be inside mautrix-go? return eventType, nil
portal.encryptLock.Lock()
encrypted, err := portal.bridge.Crypto.Encrypt(portal.MXID, eventType, *content)
portal.encryptLock.Unlock()
if err != nil {
return eventType, fmt.Errorf("failed to encrypt event: %w", err)
}
eventType = event.EventEncrypted
content.Parsed = encrypted
} }
return eventType, nil intent.AddDoublePuppetValue(content)
// TODO maybe the locking should be inside mautrix-go?
portal.encryptLock.Lock()
defer portal.encryptLock.Unlock()
err := portal.bridge.Crypto.Encrypt(portal.MXID, eventType, content)
if err != nil {
return eventType, fmt.Errorf("failed to encrypt event: %w", err)
}
return event.EventEncrypted, nil
} }
const doublePuppetKey = "fi.mau.double_puppet_source"
const doublePuppetValue = "mautrix-whatsapp"
func (portal *Portal) sendMessage(intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}, timestamp int64) (*mautrix.RespSendEvent, error) { func (portal *Portal) sendMessage(intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}, timestamp int64) (*mautrix.RespSendEvent, error) {
wrappedContent := event.Content{Parsed: content, Raw: extraContent} wrappedContent := event.Content{Parsed: content, Raw: extraContent}
if timestamp != 0 && intent.IsCustomPuppet {
if wrappedContent.Raw == nil {
wrappedContent.Raw = map[string]interface{}{}
}
if intent.IsCustomPuppet {
wrappedContent.Raw[doublePuppetKey] = doublePuppetValue
}
}
var err error var err error
eventType, err = portal.encrypt(&wrappedContent, eventType) eventType, err = portal.encrypt(intent, &wrappedContent, eventType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if eventType == event.EventEncrypted {
// Clear other custom keys if the event was encrypted, but keep the double puppet identifier
if intent.IsCustomPuppet {
wrappedContent.Raw = map[string]interface{}{doublePuppetKey: doublePuppetValue}
} else {
wrappedContent.Raw = nil
}
}
_, _ = intent.UserTyping(portal.MXID, false, 0) _, _ = intent.UserTyping(portal.MXID, false, 0)
if timestamp == 0 { if timestamp == 0 {
return intent.SendMessageEvent(portal.MXID, eventType, &wrappedContent) return intent.SendMessageEvent(portal.MXID, eventType, &wrappedContent)
@ -2142,11 +2106,11 @@ func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI,
if err != nil { if err != nil {
portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err) portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err)
if targetIntent != nil { if targetIntent != nil {
_, _ = portal.leaveWithPuppetMeta(targetIntent) _, _ = targetIntent.LeaveRoom(portal.MXID)
} }
} }
} else { } else {
_, err := portal.leaveWithPuppetMeta(targetIntent) _, err := targetIntent.LeaveRoom(portal.MXID)
if err != nil { if err != nil {
portal.log.Warnfln("Failed to leave portal as %s: %v", target, err) portal.log.Warnfln("Failed to leave portal as %s: %v", target, err)
_, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target}) _, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target})
@ -2178,19 +2142,6 @@ func (portal *Portal) HandleWhatsAppKick(source *User, senderJID types.JID, jids
} }
} }
func (portal *Portal) leaveWithPuppetMeta(intent *appservice.IntentAPI) (*mautrix.RespSendEvent, error) {
content := event.Content{
Parsed: event.MemberEventContent{
Membership: event.MembershipLeave,
},
Raw: map[string]interface{}{
doublePuppetKey: doublePuppetValue,
},
}
// Bypass IntentAPI, we don't want to EnsureJoined here
return intent.Client.SendStateEvent(portal.MXID, event.StateMember, intent.UserID.String(), &content)
}
func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, jids []types.JID) (evtID id.EventID) { func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, jids []types.JID) (evtID id.EventID) {
intent := portal.MainIntent() intent := portal.MainIntent()
if senderJID != nil && !senderJID.IsEmpty() { if senderJID != nil && !senderJID.IsEmpty() {
@ -2200,17 +2151,11 @@ func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, j
for _, jid := range jids { for _, jid := range jids {
puppet := portal.bridge.GetPuppetByJID(jid) puppet := portal.bridge.GetPuppetByJID(jid)
puppet.SyncContact(source, true, false, "handling whatsapp invite") puppet.SyncContact(source, true, false, "handling whatsapp invite")
content := event.Content{ resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &event.MemberEventContent{
Parsed: event.MemberEventContent{ Membership: event.MembershipInvite,
Membership: "invite", Displayname: puppet.Displayname,
Displayname: puppet.Displayname, AvatarURL: puppet.AvatarURL.CUString(),
AvatarURL: puppet.AvatarURL.CUString(), })
},
Raw: map[string]interface{}{
doublePuppetKey: doublePuppetValue,
},
}
resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &content)
if err != nil { if err != nil {
portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err) portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
_ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID) _ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID)
@ -3270,11 +3215,7 @@ func (portal *Portal) upsertReaction(intent *appservice.IntentAPI, targetJID typ
portal.log.Debugfln("Redacting old Matrix reaction %s after new one (%s) was sent", dbReaction.MXID, mxid) portal.log.Debugfln("Redacting old Matrix reaction %s after new one (%s) was sent", dbReaction.MXID, mxid)
var err error var err error
if intent != nil { if intent != nil {
extra := make(map[string]interface{}) _, err = intent.RedactEvent(portal.MXID, dbReaction.MXID)
if intent.IsCustomPuppet {
extra[doublePuppetKey] = doublePuppetValue
}
_, err = intent.RedactEvent(portal.MXID, dbReaction.MXID, mautrix.ReqRedact{Extra: extra})
} }
if intent == nil || errors.Is(err, mautrix.MForbidden) { if intent == nil || errors.Is(err, mautrix.MForbidden) {
_, err = portal.MainIntent().RedactEvent(portal.MXID, dbReaction.MXID) _, err = portal.MainIntent().RedactEvent(portal.MXID, dbReaction.MXID)

View file

@ -201,6 +201,16 @@ type Puppet struct {
syncLock sync.Mutex syncLock sync.Mutex
} }
var _ bridge.GhostWithProfile = (*Puppet)(nil)
func (puppet *Puppet) GetDisplayname() string {
return puppet.Displayname
}
func (puppet *Puppet) GetAvatarURL() id.ContentURI {
return puppet.AvatarURL
}
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI { func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
if puppet.customIntent == nil || portal.Key.JID == puppet.JID || (portal.Key.JID.Server == types.BroadcastServer && portal.Key.Receiver != puppet.JID) { if puppet.customIntent == nil || portal.Key.JID == puppet.JID || (portal.Key.JID.Server == types.BroadcastServer && portal.Key.Receiver != puppet.JID) {
return puppet.DefaultIntent() return puppet.DefaultIntent()

23
user.go
View file

@ -344,18 +344,15 @@ func (user *User) doPuppetResync() {
} }
func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) (ok bool) { func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) (ok bool) {
inviteContent := event.Content{ extraContent := make(map[string]interface{})
Parsed: &event.MemberEventContent{ if isDirect {
Membership: event.MembershipInvite, extraContent["is_direct"] = true
IsDirect: isDirect,
},
Raw: map[string]interface{}{},
} }
customPuppet := user.bridge.GetPuppetByCustomMXID(user.MXID) customPuppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
if customPuppet != nil && customPuppet.CustomIntent() != nil { if customPuppet != nil && customPuppet.CustomIntent() != nil {
inviteContent.Raw["fi.mau.will_auto_accept"] = true extraContent["fi.mau.will_auto_accept"] = true
} }
_, err := intent.SendStateEvent(roomID, event.StateMember, user.MXID.String(), &inviteContent) _, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{UserID: user.MXID}, extraContent)
var httpErr mautrix.HTTPError var httpErr mautrix.HTTPError
if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") { if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") {
user.bridge.StateStore.SetMembership(roomID, user.MXID, event.MembershipJoin) user.bridge.StateStore.SetMembership(roomID, user.MXID, event.MembershipJoin)
@ -981,9 +978,9 @@ func (user *User) updateChatTag(intent *appservice.IntentAPI, portal *Portal, ta
currentTag, ok := existingTags.Tags[tag] currentTag, ok := existingTags.Tags[tag]
if active && !ok { if active && !ok {
user.log.Debugln("Adding tag", tag, "to", portal.MXID) user.log.Debugln("Adding tag", tag, "to", portal.MXID)
data := CustomTagData{"0.5", doublePuppetValue} data := CustomTagData{Order: "0.5", DoublePuppet: user.bridge.Name}
err = intent.AddTagWithCustomData(portal.MXID, tag, &data) err = intent.AddTagWithCustomData(portal.MXID, tag, &data)
} else if !active && ok && currentTag.DoublePuppet == doublePuppetValue { } else if !active && ok && currentTag.DoublePuppet == user.bridge.Name {
user.log.Debugln("Removing tag", tag, "from", portal.MXID) user.log.Debugln("Removing tag", tag, "from", portal.MXID)
err = intent.RemoveTag(portal.MXID, tag) err = intent.RemoveTag(portal.MXID, tag)
} else { } else {
@ -1210,10 +1207,10 @@ func (user *User) handleReceipt(receipt *events.Receipt) {
portal.messages <- PortalMessage{receipt: receipt, source: user} portal.messages <- PortalMessage{receipt: receipt, source: user}
} }
func makeReadMarkerContent(eventID id.EventID, doublePuppet bool) CustomReadMarkers { func (user *User) makeReadMarkerContent(eventID id.EventID, doublePuppet bool) CustomReadMarkers {
var extra CustomReadReceipt var extra CustomReadReceipt
if doublePuppet { if doublePuppet {
extra.DoublePuppetSource = doublePuppetValue extra.DoublePuppetSource = user.bridge.Name
} }
return CustomReadMarkers{ return CustomReadMarkers{
ReqSetReadMarkers: mautrix.ReqSetReadMarkers{ ReqSetReadMarkers: mautrix.ReqSetReadMarkers{
@ -1235,7 +1232,7 @@ func (user *User) markSelfReadFull(portal *Portal) {
return return
} }
user.SetLastReadTS(portal.Key, lastMessage.Timestamp) user.SetLastReadTS(portal.Key, lastMessage.Timestamp)
err := puppet.CustomIntent().SetReadMarkers(portal.MXID, makeReadMarkerContent(lastMessage.MXID, true)) err := puppet.CustomIntent().SetReadMarkers(portal.MXID, user.makeReadMarkerContent(lastMessage.MXID, true))
if err != nil { if err != nil {
user.log.Warnfln("Failed to mark %s (last message) in %s as read: %v", lastMessage.MXID, portal.MXID, err) user.log.Warnfln("Failed to mark %s (last message) in %s as read: %v", lastMessage.MXID, portal.MXID, err)
} else { } else {