Add command to create WhatsApp group

This commit is contained in:
Tulir Asokan 2020-07-10 15:23:32 +03:00
parent 7eb4cfb946
commit 518cb076ff
6 changed files with 157 additions and 18 deletions

View file

@ -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,

2
go.mod
View file

@ -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
go.sum
View file

@ -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=

View file

@ -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
whatsapp-ext/group.go Normal file
View file

@ -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
}

View file

@ -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) {