From 326293303d31ef7f4413e097ee207e6793f91dab Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 25 Jun 2020 23:58:35 +0300 Subject: [PATCH] Handle WhatsApp kicks and Matrix invites --- commands.go | 4 +-- matrix.go | 6 ++++- portal.go | 64 ++++++++++++++++++++++++++++++++++++++++++-- user.go | 18 +++++++++++-- whatsapp-ext/chat.go | 11 ++++---- 5 files changed, 91 insertions(+), 12 deletions(-) diff --git a/commands.go b/commands.go index efd82db..a923854 100644 --- a/commands.go +++ b/commands.go @@ -235,7 +235,7 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) { portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(jid)) if len(portal.MXID) > 0 { portal.Sync(ce.User, whatsapp.Contact{Jid: portal.Key.JID}) - ce.Reply("Successfully joined group \"%s\" and synced portal room", portal.Name) + ce.Reply("Successfully joined group \"%s\" and synced portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID) } else { err = portal.CreateMatrixRoom(ce.User) if err != nil { @@ -243,7 +243,7 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) { return } - ce.Reply("Successfully joined group \"%s\" and created portal room", portal.Name) + ce.Reply("Successfully joined group \"%s\" and created portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID) } } diff --git a/matrix.go b/matrix.go index 319e1e1..7c3f538 100644 --- a/matrix.go +++ b/matrix.go @@ -155,8 +155,10 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) { return } + isSelf := id.UserID(evt.GetStateKey()) == evt.Sender + if content.Membership == event.MembershipLeave { - if id.UserID(evt.GetStateKey()) == evt.Sender { + if isSelf { if evt.Unsigned.PrevContent != nil { _ = evt.Unsigned.PrevContent.ParseRaw(evt.Type) prevContent, ok := evt.Unsigned.PrevContent.Parsed.(*event.MemberEventContent) @@ -169,6 +171,8 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) { } else { portal.HandleMatrixKick(user, evt) } + } else if content.Membership == event.MembershipInvite && !isSelf { + portal.HandleMatrixInvite(user, evt) } } diff --git a/portal.go b/portal.go index 148b7fe..63e2aad 100644 --- a/portal.go +++ b/portal.go @@ -1281,6 +1281,53 @@ func (portal *Portal) encryptFile(data []byte, mimeType string) ([]byte, string, return file.Encrypt(data), "application/octet-stream", file } +func (portal *Portal) tryKickUser(userID id.UserID, intent *appservice.IntentAPI) error { + _, err := intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID}) + if err != nil { + httpErr, ok := err.(mautrix.HTTPError) + if ok && httpErr.RespError != nil && httpErr.RespError.ErrCode == "M_FORBIDDEN" { + _, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID}) + } + } + return err +} + +func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI, target id.UserID, targetIntent *appservice.IntentAPI) { + if !isSameUser || targetIntent == nil { + err := portal.tryKickUser(target, kicker) + if err != nil { + portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err) + if targetIntent != nil { + _, _ = targetIntent.LeaveRoom(portal.MXID) + } + } + } else { + _, err := targetIntent.LeaveRoom(portal.MXID) + if err != nil { + portal.log.Warnfln("Failed to leave portal as %s: %v", target, err) + _, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target}) + } + } +} + +func (portal *Portal) HandleWhatsAppKick(senderJID string, jids []string) { + sender := portal.bridge.GetPuppetByJID(senderJID) + senderIntent := sender.IntentFor(portal) + for _, jid := range jids { + puppet := portal.bridge.GetPuppetByJID(jid) + portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent()) + + user := portal.bridge.GetUserByJID(jid) + if user != nil { + var customIntent *appservice.IntentAPI + if puppet.CustomMXID == user.MXID { + customIntent = puppet.CustomIntent() + } + portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent) + } + } +} + type base struct { download func() ([]byte, error) info whatsapp.MessageInfo @@ -2005,6 +2052,7 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { portal.Cleanup(false) return } else { + // TODO should we somehow deduplicate this call if this leave was sent by the bridge? resp, err := sender.Conn.LeaveGroup(portal.Key.JID) if err != nil { portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err) @@ -2015,8 +2063,8 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { } } -func (portal *Portal) HandleMatrixKick(sender *User, event *event.Event) { - puppet := portal.bridge.GetPuppetByMXID(id.UserID(event.GetStateKey())) +func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) { + puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) if puppet != nil { resp, err := sender.Conn.RemoveMember(portal.Key.JID, []string{puppet.JID}) if err != nil { @@ -2026,3 +2074,15 @@ func (portal *Portal) HandleMatrixKick(sender *User, event *event.Event) { portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp) } } + +func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) { + puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) + if puppet != nil { + resp, err := sender.Conn.AddMember(portal.Key.JID, []string{puppet.JID}) + if err != nil { + portal.log.Errorfln("Failed to add %s to group as %s: %v", puppet.JID, sender.MXID, err) + return + } + portal.log.Infoln("Add %s response: %s", puppet.JID, <-resp) + } +} diff --git a/user.go b/user.go index f5d770c..503ba70 100644 --- a/user.go +++ b/user.go @@ -861,6 +861,14 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) { portal := user.GetPortalByJID(cmd.JID) if len(portal.MXID) == 0 { + if cmd.Data.Action == whatsappExt.ChatActionIntroduce && cmd.Data.SenderJID != "unknown" { + go func() { + err := portal.CreateMatrixRoom(user) + if err != nil { + user.log.Errorln("Failed to create portal room after receiving join event:", err) + } + }() + } return } @@ -872,13 +880,19 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) { case whatsappExt.ChatActionRemoveTopic: go portal.UpdateTopic("", cmd.Data.SenderJID, true) case whatsappExt.ChatActionPromote: - go portal.ChangeAdminStatus(cmd.Data.PermissionChange.JIDs, true) + go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true) case whatsappExt.ChatActionDemote: - go portal.ChangeAdminStatus(cmd.Data.PermissionChange.JIDs, false) + go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false) case whatsappExt.ChatActionAnnounce: go portal.RestrictMessageSending(cmd.Data.Announce) case whatsappExt.ChatActionRestrict: go portal.RestrictMetadataChanges(cmd.Data.Restrict) + case whatsappExt.ChatActionRemove: + go portal.HandleWhatsAppKick(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs) + case whatsappExt.ChatActionIntroduce: + if cmd.Data.SenderJID != "unknown" { + go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID}) + } } } diff --git a/whatsapp-ext/chat.go b/whatsapp-ext/chat.go index e35ccf0..93ca56d 100644 --- a/whatsapp-ext/chat.go +++ b/whatsapp-ext/chat.go @@ -46,6 +46,7 @@ const ( ChatActionPromote ChatActionType = "promote" ChatActionDemote ChatActionType = "demote" ChatActionIntroduce ChatActionType = "introduce" + ChatActionRemove ChatActionType = "remove" ) type ChatUpdateData struct { @@ -80,7 +81,7 @@ type ChatUpdateData struct { Announce bool - PermissionChange struct { + UserChange struct { JIDs []string `json:"participants"` } } @@ -127,8 +128,8 @@ func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error { unmarshalTo = &cud.Restrict case ChatActionAnnounce: unmarshalTo = &cud.Announce - case ChatActionPromote, ChatActionDemote: - unmarshalTo = &cud.PermissionChange + case ChatActionPromote, ChatActionDemote, ChatActionRemove: + unmarshalTo = &cud.UserChange default: return nil } @@ -137,8 +138,8 @@ func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error { return err } cud.NameChange.SetBy = strings.Replace(cud.NameChange.SetBy, OldUserSuffix, NewUserSuffix, 1) - for index, jid := range cud.PermissionChange.JIDs { - cud.PermissionChange.JIDs[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1) + for index, jid := range cud.UserChange.JIDs { + cud.UserChange.JIDs[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1) } for index, jid := range cud.Introduce.SuperAdmins { cud.Introduce.SuperAdmins[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)