Browse Source

Implement joining groups and checking invite links

Tulir Asokan 3 years ago
parent
commit
1e5d5c1a3e
5 changed files with 73 additions and 48 deletions
  1. 34 27
      commands.go
  2. 1 1
      go.mod
  3. 2 2
      go.sum
  4. 21 17
      portal.go
  5. 15 1
      user.go

+ 34 - 27
commands.go

@@ -130,7 +130,7 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
 		handler.CommandLogout(ce)
 		handler.CommandLogout(ce)
 	case "toggle":
 	case "toggle":
 		handler.CommandToggle(ce)
 		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() {
 		if !ce.User.HasSession() {
 			ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.")
 			ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.")
 			return
 			return
@@ -154,6 +154,8 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
 			handler.CommandPM(ce)
 			handler.CommandPM(ce)
 		case "invite-link":
 		case "invite-link":
 			handler.CommandInviteLink(ce)
 			handler.CommandInviteLink(ce)
+		case "check-invite":
+			handler.CommandCheckInvite(ce)
 		case "join":
 		case "join":
 			handler.CommandJoin(ce)
 			handler.CommandJoin(ce)
 		case "create":
 		case "create":
@@ -223,25 +225,44 @@ func (handler *CommandHandler) CommandVersion(ce *CommandEvent) {
 	ce.Reply(fmt.Sprintf("[%s](%s) %s (%s)", Name, URL, linkifiedVersion, BuildTime))
 	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) {
 func (handler *CommandHandler) CommandInviteLink(ce *CommandEvent) {
+	reset := len(ce.Args) > 0 && strings.ToLower(ce.Args[0]) == "--reset"
 	if ce.Portal == nil {
 	if ce.Portal == nil {
 		ce.Reply("Not a portal room")
 		ce.Reply("Not a portal room")
 	} else if ce.Portal.IsPrivateChat() {
 	} else if ce.Portal.IsPrivateChat() {
 		ce.Reply("Can't get invite link to private chat")
 		ce.Reply("Can't get invite link to private chat")
 	} else if ce.Portal.IsBroadcastList() {
 	} else if ce.Portal.IsBroadcastList() {
 		ce.Reply("Can't get invite link to broadcast list")
 		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)
 		ce.Reply("Failed to get invite link: %v", err)
 	} else {
 	} else {
 		ce.Reply(link)
 		ce.Reply(link)
 	}
 	}
 }
 }
 
 
-const cmdJoinHelp = `join <invite link> - Join a group chat with an invite link.`
+const cmdCheckInviteHelp = `check-invite <invite link> - Resolve an invite link and check which group it points at.`
 const inviteLinkPrefix = "https://chat.whatsapp.com/"
 const inviteLinkPrefix = "https://chat.whatsapp.com/"
 
 
+func (handler *CommandHandler) CommandCheckInvite(ce *CommandEvent) {
+	if len(ce.Args) == 0 {
+		ce.Reply("**Usage:** `join <invite link>`")
+		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 <invite link> - Join a group chat with an invite link.`
+
 func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
 func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
 	if len(ce.Args) == 0 {
 	if len(ce.Args) == 0 {
 		ce.Reply("**Usage:** `join <invite link>`")
 		ce.Reply("**Usage:** `join <invite link>`")
@@ -251,28 +272,13 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
 		return
 		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.`
 const cmdCreateHelp = `create - Create a group chat.`
@@ -625,6 +631,7 @@ func (handler *CommandHandler) CommandHelp(ce *CommandEvent) {
 		cmdPrefix + cmdOpenHelp,
 		cmdPrefix + cmdOpenHelp,
 		cmdPrefix + cmdPMHelp,
 		cmdPrefix + cmdPMHelp,
 		cmdPrefix + cmdInviteLinkHelp,
 		cmdPrefix + cmdInviteLinkHelp,
+		cmdPrefix + cmdCheckInviteHelp,
 		cmdPrefix + cmdJoinHelp,
 		cmdPrefix + cmdJoinHelp,
 		cmdPrefix + cmdCreateHelp,
 		cmdPrefix + cmdCreateHelp,
 		cmdPrefix + cmdSetPowerLevelHelp,
 		cmdPrefix + cmdSetPowerLevelHelp,
@@ -873,7 +880,7 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) {
 			return
 			return
 		}
 		}
 	}
 	}
-	err = portal.CreateMatrixRoom(user)
+	err = portal.CreateMatrixRoom(user, nil)
 	if err != nil {
 	if err != nil {
 		ce.Reply("Failed to create portal room: %v", err)
 		ce.Reply("Failed to create portal room: %v", err)
 		return
 		return

+ 1 - 1
go.mod

@@ -8,7 +8,7 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.9
 	github.com/mattn/go-sqlite3 v1.14.9
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_golang v1.11.0
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	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
 	golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
 	google.golang.org/protobuf v1.27.1
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v2 v2.4.0

+ 2 - 2
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=
 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 h1:xpQTMgJGGaF+c8jV/LA/FVXAPJxZbSAGeflOc+Ly6uQ=
 go.mau.fi/libsignal v0.0.0-20211024113310-f9fc6a1855f2/go.mod h1:3XlVlwOfp8f9Wri+C1D4ORqgUsN4ZvunJOoPjQMBhos=
 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-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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

+ 21 - 17
portal.go

@@ -201,7 +201,7 @@ func (portal *Portal) handleMessageLoop() {
 				continue
 				continue
 			}
 			}
 			portal.log.Debugln("Creating Matrix room from incoming message")
 			portal.log.Debugln("Creating Matrix room from incoming message")
-			err := portal.CreateMatrixRoom(msg.source)
+			err := portal.CreateMatrixRoom(msg.source, nil)
 			if err != nil {
 			if err != nil {
 				portal.log.Errorln("Failed to create portal room:", err)
 				portal.log.Errorln("Failed to create portal room:", err)
 				continue
 				continue
@@ -533,7 +533,7 @@ func (portal *Portal) SyncParticipants(source *User, metadata *types.GroupInfo)
 		}
 		}
 
 
 		expectedLevel := 0
 		expectedLevel := 0
-		if participant.JID == metadata.OwnerJID {
+		if participant.JID == metadata.OwnerJID || participant.IsSuperAdmin {
 			expectedLevel = 95
 			expectedLevel = 95
 		} else if participant.IsAdmin {
 		} else if participant.IsAdmin {
 			expectedLevel = 50
 			expectedLevel = 50
@@ -737,11 +737,11 @@ func (portal *Portal) ensureUserInvited(user *User) (ok bool) {
 	return
 	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)
 	portal.log.Infoln("Syncing portal for", user.MXID)
 
 
 	if len(portal.MXID) == 0 {
 	if len(portal.MXID) == 0 {
-		err := portal.CreateMatrixRoom(user)
+		err := portal.CreateMatrixRoom(user, groupInfo)
 		if err != nil {
 		if err != nil {
 			portal.log.Errorln("Failed to create portal room:", err)
 			portal.log.Errorln("Failed to create portal room:", err)
 			return false
 			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 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}
 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()
 	portal.roomCreateLock.Lock()
 	defer portal.roomCreateLock.Unlock()
 	defer portal.roomCreateLock.Unlock()
 	if len(portal.MXID) > 0 {
 	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)
 	portal.log.Infoln("Creating Matrix room. Info source:", user.MXID)
 
 
-	var metadata *types.GroupInfo
 	//var broadcastMetadata *types.BroadcastListInfo
 	//var broadcastMetadata *types.BroadcastListInfo
 	if portal.IsPrivateChat() {
 	if portal.IsPrivateChat() {
 		puppet := portal.bridge.GetPuppetByJID(portal.Key.JID)
 		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")
 		portal.log.Debugln("Broadcast list is not yet supported, not creating room after all")
 		return fmt.Errorf("broadcast list bridging is currently not supported")
 		return fmt.Errorf("broadcast list bridging is currently not supported")
 	} else {
 	} 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)
 		portal.UpdateAvatar(user, types.EmptyJID, false)
 	}
 	}
@@ -1325,13 +1329,13 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
 	portal.ensureUserInvited(user)
 	portal.ensureUserInvited(user)
 	user.syncChatDoublePuppetDetails(portal, true)
 	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 {
 	//if broadcastMetadata != nil {

+ 15 - 1
user.go

@@ -376,7 +376,7 @@ func (user *User) handleHistorySync(evt *waProto.HistorySync) {
 				continue
 				continue
 			}
 			}
 			user.log.Debugln("Creating portal for", portal.Key.JID, "as part of history sync handling")
 			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 {
 			if err != nil {
 				user.log.Warnfln("Failed to create room for %s during backfill: %v", portal.Key.JID, err)
 				user.log.Warnfln("Failed to create room for %s during backfill: %v", portal.Key.JID, err)
 				continue
 				continue
@@ -444,6 +444,8 @@ func (user *User) HandleEvent(event interface{}) {
 		go user.syncPuppet(v.JID)
 		go user.syncPuppet(v.JID)
 	case *events.GroupInfo:
 	case *events.GroupInfo:
 		go user.handleGroupUpdate(v)
 		go user.handleGroupUpdate(v)
+	case *events.JoinedGroup:
+		go user.handleGroupCreate(v)
 	case *events.Picture:
 	case *events.Picture:
 		go user.handlePictureUpdate(v)
 		go user.handlePictureUpdate(v)
 	case *events.Receipt:
 	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) {
 func (user *User) handleGroupUpdate(evt *events.GroupInfo) {
 	portal := user.GetPortalByJID(evt.JID)
 	portal := user.GetPortalByJID(evt.JID)
 	if portal == nil || len(portal.MXID) == 0 {
 	if portal == nil || len(portal.MXID) == 0 {