From 1e5d5c1a3e21bce531eeb69856ef820b881bd443 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 31 Oct 2021 19:59:23 +0200 Subject: [PATCH] Implement joining groups and checking invite links --- commands.go | 61 +++++++++++++++++++++++++++++------------------------ go.mod | 2 +- go.sum | 4 ++-- portal.go | 38 ++++++++++++++++++--------------- user.go | 16 +++++++++++++- 5 files changed, 73 insertions(+), 48 deletions(-) diff --git a/commands.go b/commands.go index 5314c2a..0ba86b2 100644 --- a/commands.go +++ b/commands.go @@ -130,7 +130,7 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) { handler.CommandLogout(ce) case "toggle": handler.CommandToggle(ce) - case "set-relay", "unset-relay", "login-matrix", "sync", "list", "open", "pm", "invite-link", "join", "create": + case "set-relay", "unset-relay", "login-matrix", "sync", "list", "open", "pm", "invite-link", "check-invite", "join", "create": if !ce.User.HasSession() { ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.") return @@ -154,6 +154,8 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) { handler.CommandPM(ce) case "invite-link": handler.CommandInviteLink(ce) + case "check-invite": + handler.CommandCheckInvite(ce) case "join": handler.CommandJoin(ce) case "create": @@ -223,25 +225,44 @@ func (handler *CommandHandler) CommandVersion(ce *CommandEvent) { ce.Reply(fmt.Sprintf("[%s](%s) %s (%s)", Name, URL, linkifiedVersion, BuildTime)) } -const cmdInviteLinkHelp = `invite-link - Get an invite link to the current group chat.` +const cmdInviteLinkHelp = `invite-link [--reset] - Get an invite link to the current group chat, optionally regenerating the link and revoking the old link.` func (handler *CommandHandler) CommandInviteLink(ce *CommandEvent) { + reset := len(ce.Args) > 0 && strings.ToLower(ce.Args[0]) == "--reset" if ce.Portal == nil { ce.Reply("Not a portal room") } else if ce.Portal.IsPrivateChat() { ce.Reply("Can't get invite link to private chat") } else if ce.Portal.IsBroadcastList() { ce.Reply("Can't get invite link to broadcast list") - } else if link, err := ce.User.Client.GetGroupInviteLink(ce.Portal.Key.JID); err != nil { + } else if link, err := ce.User.Client.GetGroupInviteLink(ce.Portal.Key.JID, reset); err != nil { ce.Reply("Failed to get invite link: %v", err) } else { ce.Reply(link) } } -const cmdJoinHelp = `join - Join a group chat with an invite link.` +const cmdCheckInviteHelp = `check-invite - Resolve an invite link and check which group it points at.` const inviteLinkPrefix = "https://chat.whatsapp.com/" +func (handler *CommandHandler) CommandCheckInvite(ce *CommandEvent) { + if len(ce.Args) == 0 { + ce.Reply("**Usage:** `join `") + return + } else if len(ce.Args[0]) <= len(inviteLinkPrefix) || ce.Args[0][:len(inviteLinkPrefix)] != inviteLinkPrefix { + ce.Reply("That doesn't look like a WhatsApp invite link") + return + } + group, err := ce.User.Client.GetGroupInfoFromLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to get group info: %v", err) + return + } + ce.Reply("That invite link points at %s (`%s`)", group.Name, group.JID) +} + +const cmdJoinHelp = `join - Join a group chat with an invite link.` + func (handler *CommandHandler) CommandJoin(ce *CommandEvent) { if len(ce.Args) == 0 { ce.Reply("**Usage:** `join `") @@ -251,28 +272,13 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) { return } - ce.Reply("Not yet implemented") - // TODO reimplement - //jid, err := ce.User.Conn.GroupAcceptInviteCode(ce.Args[0][len(inviteLinkPrefix):]) - //if err != nil { - // ce.Reply("Failed to join group: %v", err) - // return - //} - // - //handler.log.Debugln("%s successfully joined group %s", ce.User.MXID, jid) - //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: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID) - //} else { - // err = portal.CreateMatrixRoom(ce.User) - // if err != nil { - // ce.Reply("Failed to create portal room: %v", err) - // return - // } - // - // ce.Reply("Successfully joined group \"%s\" and created portal room: [%s](https://matrix.to/#/%s)", portal.Name, portal.Name, portal.MXID) - //} + jid, err := ce.User.Client.JoinGroupViaLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to join group: %v", err) + return + } + handler.log.Debugln("%s successfully joined group %s", ce.User.MXID, jid) + ce.Reply("Successfully joined group `%s`, the portal should be created momentarily", jid) } const cmdCreateHelp = `create - Create a group chat.` @@ -625,6 +631,7 @@ func (handler *CommandHandler) CommandHelp(ce *CommandEvent) { cmdPrefix + cmdOpenHelp, cmdPrefix + cmdPMHelp, cmdPrefix + cmdInviteLinkHelp, + cmdPrefix + cmdCheckInviteHelp, cmdPrefix + cmdJoinHelp, cmdPrefix + cmdCreateHelp, cmdPrefix + cmdSetPowerLevelHelp, @@ -873,7 +880,7 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) { return } } - err = portal.CreateMatrixRoom(user) + err = portal.CreateMatrixRoom(user, nil) if err != nil { ce.Reply("Failed to create portal room: %v", err) return diff --git a/go.mod b/go.mod index ac0f0b9..55fcc57 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ 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-20211031131127-03a69c3e343b + go.mau.fi/whatsmeow v0.0.0-20211031175440-39cd01efeed7 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index d2d1589..254c11f 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-20211031131127-03a69c3e343b h1:GvVBHYS4iBduhXtsPsnqJtrt8BP1OqYp6OdZqYtt/xY= -go.mau.fi/whatsmeow v0.0.0-20211031131127-03a69c3e343b/go.mod h1:ODEmmqeUn9eBDQHFc1S902YA3YFLtmaBujYRRFl53jI= +go.mau.fi/whatsmeow v0.0.0-20211031175440-39cd01efeed7 h1:AxqjTj5ejuTUGrpG21Ot/dIjY946OjveZM08SACeDhw= +go.mau.fi/whatsmeow v0.0.0-20211031175440-39cd01efeed7/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= diff --git a/portal.go b/portal.go index 10652eb..26fa653 100644 --- a/portal.go +++ b/portal.go @@ -201,7 +201,7 @@ func (portal *Portal) handleMessageLoop() { continue } portal.log.Debugln("Creating Matrix room from incoming message") - err := portal.CreateMatrixRoom(msg.source) + err := portal.CreateMatrixRoom(msg.source, nil) if err != nil { portal.log.Errorln("Failed to create portal room:", err) continue @@ -533,7 +533,7 @@ func (portal *Portal) SyncParticipants(source *User, metadata *types.GroupInfo) } expectedLevel := 0 - if participant.JID == metadata.OwnerJID { + if participant.JID == metadata.OwnerJID || participant.IsSuperAdmin { expectedLevel = 95 } else if participant.IsAdmin { expectedLevel = 50 @@ -737,11 +737,11 @@ func (portal *Portal) ensureUserInvited(user *User) (ok bool) { return } -func (portal *Portal) Sync(user *User) bool { +func (portal *Portal) Sync(user *User, groupInfo *types.GroupInfo) bool { portal.log.Infoln("Syncing portal for", user.MXID) if len(portal.MXID) == 0 { - err := portal.CreateMatrixRoom(user) + err := portal.CreateMatrixRoom(user, groupInfo) if err != nil { portal.log.Errorln("Failed to create portal room:", err) return false @@ -1197,7 +1197,7 @@ var BackfillDummyStateEvent = event.Type{Type: "fi.mau.dummy.blank_backfill_stat var BackfillEndDummyEvent = event.Type{Type: "fi.mau.dummy.backfill_end", Class: event.MessageEventType} var ForwardBackfillDummyEvent = event.Type{Type: "fi.mau.dummy.pre_forward_backfill", Class: event.MessageEventType} -func (portal *Portal) CreateMatrixRoom(user *User) error { +func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo) error { portal.roomCreateLock.Lock() defer portal.roomCreateLock.Unlock() if len(portal.MXID) > 0 { @@ -1211,7 +1211,6 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { portal.log.Infoln("Creating Matrix room. Info source:", user.MXID) - var metadata *types.GroupInfo //var broadcastMetadata *types.BroadcastListInfo if portal.IsPrivateChat() { puppet := portal.bridge.GetPuppetByJID(portal.Key.JID) @@ -1249,11 +1248,16 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { portal.log.Debugln("Broadcast list is not yet supported, not creating room after all") return fmt.Errorf("broadcast list bridging is currently not supported") } else { - var err error - metadata, err = user.Client.GetGroupInfo(portal.Key.JID) - if err == nil { - portal.Name = metadata.Name - portal.Topic = metadata.Topic + if groupInfo == nil { + var err error + groupInfo, err = user.Client.GetGroupInfo(portal.Key.JID) + if err != nil { + portal.log.Warnfln("Failed to get group info through %s: %v", user.JID, err) + } + } + if groupInfo != nil { + portal.Name = groupInfo.Name + portal.Topic = groupInfo.Topic } portal.UpdateAvatar(user, types.EmptyJID, false) } @@ -1325,13 +1329,13 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { portal.ensureUserInvited(user) user.syncChatDoublePuppetDetails(portal, true) - if metadata != nil { - portal.SyncParticipants(user, metadata) - if metadata.IsAnnounce { - portal.RestrictMessageSending(metadata.IsAnnounce) + if groupInfo != nil { + portal.SyncParticipants(user, groupInfo) + if groupInfo.IsAnnounce { + portal.RestrictMessageSending(groupInfo.IsAnnounce) } - if metadata.IsLocked { - portal.RestrictMetadataChanges(metadata.IsLocked) + if groupInfo.IsLocked { + portal.RestrictMetadataChanges(groupInfo.IsLocked) } } //if broadcastMetadata != nil { diff --git a/user.go b/user.go index 795962a..659cfb4 100644 --- a/user.go +++ b/user.go @@ -376,7 +376,7 @@ func (user *User) handleHistorySync(evt *waProto.HistorySync) { continue } user.log.Debugln("Creating portal for", portal.Key.JID, "as part of history sync handling") - err = portal.CreateMatrixRoom(user) + err = portal.CreateMatrixRoom(user, nil) if err != nil { user.log.Warnfln("Failed to create room for %s during backfill: %v", portal.Key.JID, err) continue @@ -444,6 +444,8 @@ func (user *User) HandleEvent(event interface{}) { go user.syncPuppet(v.JID) case *events.GroupInfo: go user.handleGroupUpdate(v) + case *events.JoinedGroup: + go user.handleGroupCreate(v) case *events.Picture: go user.handlePictureUpdate(v) case *events.Receipt: @@ -754,6 +756,18 @@ func (user *User) markSelfReadFull(portal *Portal) { } } +func (user *User) handleGroupCreate(evt *events.JoinedGroup) { + portal := user.GetPortalByJID(evt.JID) + if len(portal.MXID) == 0 { + err := portal.CreateMatrixRoom(user, &evt.GroupInfo) + if err != nil { + user.log.Errorln("Failed to create Matrix room after join notification: %v", err) + } + } else { + portal.Sync(user, &evt.GroupInfo) + } +} + func (user *User) handleGroupUpdate(evt *events.GroupInfo) { portal := user.GetPortalByJID(evt.JID) if portal == nil || len(portal.MXID) == 0 {