Jelajahi Sumber

Add command to create WhatsApp group

Tulir Asokan 5 tahun lalu
induk
melakukan
518cb076ff
6 mengubah file dengan 157 tambahan dan 18 penghapusan
  1. 73 1
      commands.go
  2. 1 1
      go.mod
  3. 2 0
      go.sum
  4. 12 14
      matrix.go
  5. 68 0
      whatsapp-ext/group.go
  6. 1 2
      whatsapp-ext/whatsapp.go

+ 73 - 1
commands.go

@@ -131,7 +131,7 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
 		handler.CommandLogout(ce)
 	case "toggle-presence":
 		handler.CommandPresence(ce)
-	case "login-matrix", "sync", "list", "open", "pm", "invite-link", "join":
+	case "login-matrix", "sync", "list", "open", "pm", "invite-link", "join", "create":
 		if !ce.User.HasSession() {
 			ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.")
 			return
@@ -155,6 +155,8 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
 			handler.CommandInviteLink(ce)
 		case "join":
 			handler.CommandJoin(ce)
+		case "create":
+			handler.CommandCreate(ce)
 		}
 	default:
 		ce.Reply("Unknown Command")
@@ -249,6 +251,75 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
 	}
 }
 
+const cmdCreateHelp = `create - Create a group chat.`
+
+func (handler *CommandHandler) CommandCreate(ce *CommandEvent) {
+	if ce.Portal != nil {
+		ce.Reply("This is already a portal room")
+		return
+	}
+
+	members, err := ce.Bot.JoinedMembers(ce.RoomID)
+	if err != nil {
+		ce.Reply("Failed to get room members: %v", err)
+		return
+	}
+
+	var roomNameEvent event.RoomNameEventContent
+	err = ce.Bot.StateEvent(ce.RoomID, event.StateRoomName, "", &roomNameEvent)
+	if err != nil {
+		ce.Reply("Failed to get room name")
+		return
+	} else if len(roomNameEvent.Name) == 0 {
+		ce.Reply("Please set a name for the room first")
+		return
+	}
+
+	var encryptionEvent event.EncryptionEventContent
+	err = ce.Bot.StateEvent(ce.RoomID, event.StateEncryption, "", &encryptionEvent)
+	if err != nil {
+		ce.Reply("Failed to get room encryption status")
+		return
+	}
+
+	participants := []string{ce.User.JID}
+	for userID := range members.Joined {
+		jid, ok := handler.bridge.ParsePuppetMXID(userID)
+		if ok && jid != ce.User.JID {
+			participants = append(participants, jid)
+		}
+	}
+
+	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)
+	ce.User.addPortalToCommunity(portal)
+}
+
 const cmdSetPowerLevelHelp = `set-pl [user ID] <power level> - Change the power level in a portal room. Only for bridge admins.`
 
 func (handler *CommandHandler) CommandSetPowerLevel(ce *CommandEvent) {
@@ -540,6 +611,7 @@ func (handler *CommandHandler) CommandHelp(ce *CommandEvent) {
 		cmdPrefix + cmdPMHelp,
 		cmdPrefix + cmdInviteLinkHelp,
 		cmdPrefix + cmdJoinHelp,
+		cmdPrefix + cmdCreateHelp,
 		cmdPrefix + cmdSetPowerLevelHelp,
 		cmdPrefix + cmdDeletePortalHelp,
 		cmdPrefix + cmdDeleteAllPortalsHelp,

+ 1 - 1
go.mod

@@ -16,7 +16,7 @@ require (
 	gopkg.in/yaml.v2 v2.3.0
 	maunium.net/go/mauflag v1.0.0
 	maunium.net/go/maulogger/v2 v2.1.1
-	maunium.net/go/mautrix v0.5.7
+	maunium.net/go/mautrix v0.5.8
 )
 
 replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.4

+ 2 - 0
go.sum

@@ -202,3 +202,5 @@ maunium.net/go/mautrix v0.5.6 h1:XCpyj3yeSOXpX+HMbF+3rdja97efMv/XchsOHylKdXY=
 maunium.net/go/mautrix v0.5.6/go.mod h1:FLbMANzwqlsX2Fgm7SDe+E4I3wSa4UxJRKqS5wGkCwA=
 maunium.net/go/mautrix v0.5.7 h1:tyRwllz3SZvMfD2YjaJPWopxmUCxZgQ2hl5/3/loHTE=
 maunium.net/go/mautrix v0.5.7/go.mod h1:FLbMANzwqlsX2Fgm7SDe+E4I3wSa4UxJRKqS5wGkCwA=
+maunium.net/go/mautrix v0.5.8 h1:jOE3U8WYSIc4qbYvyVaDhOaQcB3sDPN5A2zQ93YixZ0=
+maunium.net/go/mautrix v0.5.8/go.mod h1:Va/74MijqaS0DQ3aUqxmFO54/PMfr1LVsCOcGRHbYmo=

+ 12 - 14
matrix.go

@@ -132,19 +132,25 @@ func (mx *MatrixHandler) HandleBotInvite(evt *event.Event) {
 		return
 	}
 
-	if !hasPuppets {
-		user := mx.bridge.GetUserByMXID(evt.Sender)
+	if !hasPuppets && (len(user.ManagementRoom) == 0 || evt.Content.AsMember().IsDirect) {
 		user.SetManagementRoom(evt.RoomID)
 		_, _ = intent.SendNotice(user.ManagementRoom, "This room has been registered as your bridge management/status room. Send `help` to get a list of commands.")
 		mx.log.Debugln(evt.RoomID, "registered as a management room with", evt.Sender)
 	}
 }
 
-func (mx *MatrixHandler) handleExistingPrivatePortal(roomID id.RoomID, inviter *User, puppet *Puppet, portal *Portal) {
+func (mx *MatrixHandler) handlePrivatePortal(roomID id.RoomID, inviter *User, puppet *Puppet, key database.PortalKey) {
+	portal := mx.bridge.GetPortalByJID(key)
+
+	if len(portal.MXID) == 0 {
+		mx.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
+		return
+	}
+
 	err := portal.MainIntent().EnsureInvited(portal.MXID, inviter.MXID)
 	if err != nil {
 		mx.log.Warnfln("Failed to invite %s to existing private chat portal %s with %s: %v. Redirecting portal to new room...", inviter.MXID, portal.MXID, puppet.JID, err)
-		mx.createPrivatePortalFromInvite(portal.Key, roomID, inviter, puppet, portal)
+		mx.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
 		return
 	}
 	intent := puppet.DefaultIntent()
@@ -153,10 +159,7 @@ func (mx *MatrixHandler) handleExistingPrivatePortal(roomID id.RoomID, inviter *
 	_, _ = intent.LeaveRoom(roomID)
 }
 
-func (mx *MatrixHandler) createPrivatePortalFromInvite(key database.PortalKey, roomID id.RoomID, inviter *User, puppet *Puppet, portal *Portal) {
-	if portal == nil {
-		portal = mx.bridge.NewManualPortal(key)
-	}
+func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter *User, puppet *Puppet, portal *Portal) {
 	portal.MXID = roomID
 	portal.Topic = "WhatsApp private chat"
 	_, _ = portal.MainIntent().SetRoomTopic(portal.MXID, portal.Topic)
@@ -221,12 +224,7 @@ func (mx *MatrixHandler) HandlePuppetInvite(evt *event.Event, inviter *User, pup
 	}
 	if !hasBridgeBot && !hasOtherUsers {
 		key := database.NewPortalKey(puppet.JID, inviter.JID)
-		existingPortal := mx.bridge.GetPortalByJID(key)
-		if existingPortal != nil && len(existingPortal.MXID) > 0 {
-			mx.handleExistingPrivatePortal(evt.RoomID, inviter, puppet, existingPortal)
-		} else {
-			mx.createPrivatePortalFromInvite(key, evt.RoomID, inviter, puppet, existingPortal)
-		}
+		mx.handlePrivatePortal(evt.RoomID, inviter, puppet, key)
 	} else if !hasBridgeBot {
 		mx.log.Debugln("Leaving multi-user room", evt.RoomID, "as", puppet.MXID, "after accepting invite from", evt.Sender)
 		_, _ = intent.SendNotice(evt.RoomID, "Please invite the bridge bot first if you want to bridge to a WhatsApp group.")

+ 68 - 0
whatsapp-ext/group.go

@@ -0,0 +1,68 @@
+// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
+// Copyright (C) 2019 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package whatsappExt
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"maunium.net/go/mautrix-whatsapp/types"
+)
+
+type CreateGroupResponse struct {
+	Status       int              `json:"status"`
+	GroupID      types.WhatsAppID `json:"gid"`
+	Participants map[types.WhatsAppID]struct {
+		Code string `json:"code"`
+	} `json:"participants"`
+
+	Source string `json:"-"`
+}
+
+type actualCreateGroupResponse struct {
+	Status       int              `json:"status"`
+	GroupID      types.WhatsAppID `json:"gid"`
+	Participants []map[types.WhatsAppID]struct {
+		Code string `json:"code"`
+	} `json:"participants"`
+}
+
+func (ext *ExtendedConn) CreateGroup(subject string, participants []types.WhatsAppID) (*CreateGroupResponse, error) {
+	respChan, err := ext.Conn.CreateGroup(subject, participants)
+	if err != nil {
+		return nil, err
+	}
+	var resp CreateGroupResponse
+	var actualResp actualCreateGroupResponse
+	resp.Source = <-respChan
+	fmt.Println(">>>>>>", resp.Source)
+	err = json.Unmarshal([]byte(resp.Source), &actualResp)
+	if err != nil {
+		return nil, err
+	}
+	resp.Status = actualResp.Status
+	resp.GroupID = actualResp.GroupID
+	resp.Participants = make(map[types.WhatsAppID]struct {
+		Code string `json:"code"`
+	})
+	for _, participantMap := range actualResp.Participants {
+		for jid, status := range participantMap {
+			resp.Participants[jid] = status
+		}
+	}
+	return &resp, nil
+}

+ 1 - 2
whatsapp-ext/whatsapp.go

@@ -51,7 +51,6 @@ func (ext *ExtendedConn) AddHandler(handler whatsapp.Handler) {
 	ext.handlers = append(ext.handlers, handler)
 }
 
-
 func (ext *ExtendedConn) RemoveHandler(handler whatsapp.Handler) bool {
 	ext.Conn.RemoveHandler(handler)
 	for i, v := range ext.handlers {
@@ -127,7 +126,7 @@ type ProfilePicInfo struct {
 	URL string `json:"eurl"`
 	Tag string `json:"tag"`
 
-	Status int16 `json:"status"`
+	Status int `json:"status"`
 }
 
 func (ppi *ProfilePicInfo) Download() (io.ReadCloser, error) {