diff --git a/commands.go b/commands.go index 270689e..29e29f2 100644 --- a/commands.go +++ b/commands.go @@ -323,6 +323,7 @@ func (handler *CommandHandler) CommandCreate(ce *CommandEvent) { var roomNameEvent event.RoomNameEventContent err = ce.Bot.StateEvent(ce.RoomID, event.StateRoomName, "", &roomNameEvent) if err != nil && !errors.Is(err, mautrix.MNotFound) { + handler.log.Errorln("Failed to get room name to create group:", err) ce.Reply("Failed to get room name") return } else if len(roomNameEvent.Name) == 0 { @@ -337,45 +338,57 @@ func (handler *CommandHandler) CommandCreate(ce *CommandEvent) { return } - participants := []types.JID{ce.User.JID.ToNonAD()} + var participants []types.JID + participantDedup := make(map[types.JID]bool) + participantDedup[ce.User.JID.ToNonAD()] = true + participantDedup[types.EmptyJID] = true for userID := range members.Joined { jid, ok := handler.bridge.ParsePuppetMXID(userID) - if ok && jid.User != ce.User.JID.User { + if !ok { + user := handler.bridge.GetUserByMXID(userID) + if user != nil && !user.JID.IsEmpty() { + jid = user.JID.ToNonAD() + } + } + if !participantDedup[jid] { + participantDedup[jid] = true participants = append(participants, jid) } } - ce.Reply("Not yet implemented") - // TODO reimplement - //resp, err := ce.User.Conn.CreateGroup(roomNameEvent.Name, participants) - //if err != nil { - // ce.Reply("Failed to create group: %v", err) - // return - //} - //portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(resp.GroupID)) - //portal.roomCreateLock.Lock() - //defer portal.roomCreateLock.Unlock() - //if len(portal.MXID) != 0 { - // portal.log.Warnln("Detected race condition in room creation") - // // TODO race condition, clean up the old room - //} - //portal.MXID = ce.RoomID - //portal.Name = roomNameEvent.Name - //portal.Encrypted = encryptionEvent.Algorithm == id.AlgorithmMegolmV1 - //if !portal.Encrypted && handler.bridge.Config.Bridge.Encryption.Default { - // _, err = portal.MainIntent().SendStateEvent(portal.MXID, event.StateEncryption, "", &event.EncryptionEventContent{Algorithm: id.AlgorithmMegolmV1}) - // if err != nil { - // portal.log.Warnln("Failed to enable e2be:", err) - // } - // portal.Encrypted = true - //} - // - //portal.Update() - //portal.UpdateBridgeInfo() - // - //ce.Reply("Successfully created WhatsApp group %s", portal.Key.JID) - //inCommunity := ce.User.addPortalToCommunity(portal) - //ce.User.CreateUserPortal(database.PortalKeyWithMeta{PortalKey: portal.Key, InCommunity: inCommunity}) + handler.log.Infofln("Creating group for %s with name %s and participants %+v", ce.RoomID, roomNameEvent.Name, participants) + resp, err := ce.User.Client.CreateGroup(roomNameEvent.Name, participants) + if err != nil { + ce.Reply("Failed to create group: %v", err) + return + } + portal := ce.User.GetPortalByJID(resp.JID) + portal.roomCreateLock.Lock() + defer portal.roomCreateLock.Unlock() + if len(portal.MXID) != 0 { + portal.log.Warnln("Detected race condition in room creation") + // TODO race condition, clean up the old room + } + portal.MXID = ce.RoomID + portal.Name = roomNameEvent.Name + portal.Encrypted = encryptionEvent.Algorithm == id.AlgorithmMegolmV1 + if !portal.Encrypted && handler.bridge.Config.Bridge.Encryption.Default { + _, err = portal.MainIntent().SendStateEvent(portal.MXID, event.StateEncryption, "", &event.EncryptionEventContent{Algorithm: id.AlgorithmMegolmV1}) + if err != nil { + portal.log.Warnln("Failed to enable encryption in room:", err) + if errors.Is(err, mautrix.MForbidden) { + ce.Reply("I don't seem to have permission to enable encryption in this room.") + } else { + ce.Reply("Failed to enable encryption in room: %v", err) + } + } + portal.Encrypted = true + } + + portal.Update() + portal.UpdateBridgeInfo() + + ce.Reply("Successfully created WhatsApp group %s", portal.Key.JID) } func parseInviteMeta(meta map[string]interface{}) (jid, inviter types.JID, code string, expiration int64, ok bool) { diff --git a/database/puppet.go b/database/puppet.go index 186ba67..57ddf8b 100644 --- a/database/puppet.go +++ b/database/puppet.go @@ -138,6 +138,6 @@ func (puppet *Puppet) Update() { _, err := puppet.db.Exec("UPDATE puppet SET displayname=$1, name_quality=$2, avatar=$3, avatar_url=$4, custom_mxid=$5, access_token=$6, next_batch=$7, enable_presence=$8, enable_receipts=$9 WHERE username=$10", puppet.Displayname, puppet.NameQuality, puppet.Avatar, puppet.AvatarURL.String(), puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch, puppet.EnablePresence, puppet.EnableReceipts, puppet.JID.User) if err != nil { - puppet.log.Warnfln("Failed to update %s->%s: %v", puppet.JID, err) + puppet.log.Warnfln("Failed to update %s: %v", puppet.JID, err) } } diff --git a/database/statestore.go b/database/statestore.go index e76d60f..94ebae8 100644 --- a/database/statestore.go +++ b/database/statestore.go @@ -218,13 +218,13 @@ func (store *SQLStateStore) GetPowerLevels(roomID id.RoomID) (levels *event.Powe var data []byte err := row.Scan(&data) if err != nil { - store.log.Errorln("Failed to scan power levels of %s: %v", roomID, err) + store.log.Errorfln("Failed to scan power levels of %s: %v", roomID, err) return } levels = &event.PowerLevelsEventContent{} err = json.Unmarshal(data, levels) if err != nil { - store.log.Errorln("Failed to parse power levels of %s: %v", roomID, err) + store.log.Errorfln("Failed to parse power levels of %s: %v", roomID, err) return nil } return @@ -242,7 +242,7 @@ func (store *SQLStateStore) GetPowerLevel(roomID id.RoomID, userID id.UserID) in var powerLevel int err := row.Scan(&powerLevel) if err != nil { - store.log.Errorln("Failed to scan power level of %s in %s: %v", userID, roomID, err) + store.log.Errorfln("Failed to scan power level of %s in %s: %v", userID, roomID, err) } return powerLevel } @@ -267,7 +267,7 @@ func (store *SQLStateStore) GetPowerLevelRequirement(roomID id.RoomID, eventType var powerLevel int err := row.Scan(&powerLevel) if err != nil { - store.log.Errorln("Failed to scan power level for %s in %s: %v", eventType, roomID, err) + store.log.Errorfln("Failed to scan power level for %s in %s: %v", eventType, roomID, err) } return powerLevel } @@ -294,7 +294,7 @@ func (store *SQLStateStore) HasPowerLevel(roomID id.RoomID, userID id.UserID, ev var hasPower bool err := row.Scan(&hasPower) if err != nil { - store.log.Errorln("Failed to scan power level for %s in %s: %v", eventType, roomID, err) + store.log.Errorfln("Failed to scan power level for %s in %s: %v", eventType, roomID, err) } return hasPower } diff --git a/go.mod b/go.mod index c38a044..22d1540 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,13 @@ require ( github.com/mattn/go-sqlite3 v1.14.9 github.com/prometheus/client_golang v1.11.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - go.mau.fi/whatsmeow v0.0.0-20211103125516-00d0df2dd132 + go.mau.fi/whatsmeow v0.0.0-20211105101612-33697884c521 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 maunium.net/go/mauflag v1.0.0 maunium.net/go/maulogger/v2 v2.3.1 - maunium.net/go/mautrix v0.10.1-0.20211103193019-010782c6021e + maunium.net/go/mautrix v0.10.1 ) require ( diff --git a/go.sum b/go.sum index 5c54a15..31f62b6 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/tidwall/sjson v1.2.3 h1:5+deguEhHSEjmuICXZ21uSSsXotWMA0orU783+Z7Cp8= github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs= go.mau.fi/libsignal v0.0.0-20211024113310-f9fc6a1855f2 h1:xpQTMgJGGaF+c8jV/LA/FVXAPJxZbSAGeflOc+Ly6uQ= go.mau.fi/libsignal v0.0.0-20211024113310-f9fc6a1855f2/go.mod h1:3XlVlwOfp8f9Wri+C1D4ORqgUsN4ZvunJOoPjQMBhos= -go.mau.fi/whatsmeow v0.0.0-20211103125516-00d0df2dd132 h1:wgpiQPdoKCTyhK/GfNfeNhbXjG5A3Fjkbfsmww6ojcY= -go.mau.fi/whatsmeow v0.0.0-20211103125516-00d0df2dd132/go.mod h1:ODEmmqeUn9eBDQHFc1S902YA3YFLtmaBujYRRFl53jI= +go.mau.fi/whatsmeow v0.0.0-20211105101612-33697884c521 h1:rkqvvX5qemi5DHMcwS5T/NmYBrzX3TAqKLW7P5lyHZg= +go.mau.fi/whatsmeow v0.0.0-20211105101612-33697884c521/go.mod h1:ODEmmqeUn9eBDQHFc1S902YA3YFLtmaBujYRRFl53jI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -219,5 +219,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/maulogger/v2 v2.3.1 h1:fwBYJne0pHvJrrIPHK+TAPfyxxbBEz46oVGez2x0ODE= maunium.net/go/maulogger/v2 v2.3.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= -maunium.net/go/mautrix v0.10.1-0.20211103193019-010782c6021e h1:yvKCw2P/nHEfjvKAVA8aokbe1UYIgJcswnv50EZaSRU= -maunium.net/go/mautrix v0.10.1-0.20211103193019-010782c6021e/go.mod h1:k4Ng5oci83MEbqPL6KOjPdbU7f8v01KlMjR/zTQ+7mA= +maunium.net/go/mautrix v0.10.1 h1:sJeygU2CpvoeHKpCOLxUstclHG740IeBfTtKgKnC+nU= +maunium.net/go/mautrix v0.10.1/go.mod h1:k4Ng5oci83MEbqPL6KOjPdbU7f8v01KlMjR/zTQ+7mA= diff --git a/portal.go b/portal.go index 369a902..d56db6f 100644 --- a/portal.go +++ b/portal.go @@ -1423,10 +1423,10 @@ func (portal *Portal) HandleWhatsAppKick(source *User, senderJID types.JID, jids sender := portal.bridge.GetPuppetByJID(senderJID) senderIntent := sender.IntentFor(portal) for _, jid := range jids { - if source != nil && source.JID.User == jid.User { - portal.log.Debugln("Ignoring self-kick by", source.MXID) - continue - } + //if source != nil && source.JID.User == jid.User { + // portal.log.Debugln("Ignoring self-kick by", source.MXID) + // continue + //} puppet := portal.bridge.GetPuppetByJID(jid) portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent()) @@ -1452,7 +1452,8 @@ func (portal *Portal) leaveWithPuppetMeta(intent *appservice.IntentAPI) (*mautri doublePuppetField: true, }, } - return intent.SendStateEvent(portal.MXID, event.StateMember, intent.UserID.String(), &content) + // 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) { @@ -2250,13 +2251,11 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { portal.Cleanup(false) return } else if portal.bridge.Config.Bridge.BridgeMatrixLeave { - // TODO should we somehow deduplicate this call if this leave was sent by the bridge? - // FIXME reimplement - //resp, err := sender.Client.LeaveGroup(portal.Key.JID) - //if err != nil { - // portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err) - // return - //} + err := sender.Client.LeaveGroup(portal.Key.JID) + if err != nil { + portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err) + return + } //portal.log.Infoln("Leave response:", <-resp) } portal.CleanupIfEmpty() @@ -2265,12 +2264,13 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) { puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) if puppet != nil { - // FIXME reimplement - //resp, err := sender.Conn.RemoveMember(portal.Key.JID, []string{puppet.JID}) - //if err != nil { - // portal.log.Errorfln("Failed to kick %s from group as %s: %v", puppet.JID, sender.MXID, err) - // return - //} + _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{ + puppet.JID: whatsmeow.ParticipantChangeRemove, + }) + if err != nil { + portal.log.Errorfln("Failed to kick %s from group as %s: %v", puppet.JID, sender.MXID, err) + return + } //portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp) } } @@ -2278,12 +2278,13 @@ func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) { func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) { puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) if puppet != nil { - // FIXME reimplement - //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 - //} + _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{ + puppet.JID: whatsmeow.ParticipantChangeAdd, + }) + if err != nil { + portal.log.Errorfln("Failed to add %s to group as %s: %v", puppet.JID, sender.MXID, err) + return + } //portal.log.Infofln("Add %s response: %s", puppet.JID, <-resp) } } @@ -2295,17 +2296,16 @@ func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) { if content.Name == portal.Name { return } - // FIXME reimplement - //portal.Name = content.Name - //resp, err = sender.Conn.UpdateGroupSubject(content.Name, portal.Key.JID) + portal.Name = content.Name + err = sender.Client.SetGroupName(portal.Key.JID, content.Name) case *event.TopicEventContent: if content.Topic == portal.Topic { return } - // FIXME reimplement - //portal.Topic = content.Topic - //resp, err = sender.Conn.UpdateGroupDescription(sender.JID, portal.Key.JID, content.Topic) + portal.Topic = content.Topic + err = sender.Client.SetGroupTopic(portal.Key.JID, "", "", content.Topic) case *event.RoomAvatarEventContent: + // TODO implement return } if err != nil {