Prechádzať zdrojové kódy

Move Matrix event and command handling to mautrix-go

Tulir Asokan 3 rokov pred
rodič
commit
73304cd400
10 zmenil súbory, kde vykonal 483 pridanie a 875 odobranie
  1. 328 363
      commands.go
  2. 9 6
      config/bridge.go
  3. 1 1
      custompuppet.go
  4. 1 1
      go.mod
  5. 2 2
      go.sum
  6. 15 10
      main.go
  7. 20 476
      matrix.go
  8. 58 12
      portal.go
  9. 26 0
      puppet.go
  10. 23 4
      user.go

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 328 - 363
commands.go


+ 9 - 6
config/bridge.go

@@ -112,12 +112,7 @@ type BridgeConfig struct {
 
 	CommandPrefix string `yaml:"command_prefix"`
 
-	ManagementRoomText struct {
-		Welcome            string `yaml:"welcome"`
-		WelcomeConnected   string `yaml:"welcome_connected"`
-		WelcomeUnconnected string `yaml:"welcome_unconnected"`
-		AdditionalHelp     string `yaml:"additional_help"`
-	} `yaml:"management_room_text"`
+	ManagementRoomText bridgeconfig.ManagementRoomTexts `yaml:"management_room_text"`
 
 	Encryption bridgeconfig.EncryptionConfig `yaml:"encryption"`
 
@@ -138,6 +133,14 @@ func (bc BridgeConfig) GetEncryptionConfig() bridgeconfig.EncryptionConfig {
 	return bc.Encryption
 }
 
+func (bc BridgeConfig) GetCommandPrefix() string {
+	return bc.CommandPrefix
+}
+
+func (bc BridgeConfig) GetManagementRoomTexts() bridgeconfig.ManagementRoomTexts {
+	return bc.ManagementRoomText
+}
+
 type umBridgeConfig BridgeConfig
 
 func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {

+ 1 - 1
custompuppet.go

@@ -219,7 +219,7 @@ func (puppet *Puppet) ProcessResponse(resp *mautrix.RespSync, _ string) error {
 			if err != nil {
 				continue
 			}
-			go puppet.bridge.MatrixHandler.HandlePresence(evt)
+			go puppet.bridge.HandlePresence(evt)
 		}
 	}
 	return nil

+ 1 - 1
go.mod

@@ -15,7 +15,7 @@ require (
 	golang.org/x/net v0.0.0-20220513224357-95641704303c
 	google.golang.org/protobuf v1.28.0
 	maunium.net/go/maulogger/v2 v2.3.2
-	maunium.net/go/mautrix v0.11.1-0.20220521221850-b3037c19004a
+	maunium.net/go/mautrix v0.11.1-0.20220522131515-87f89ec33247
 )
 
 require (

+ 2 - 2
go.sum

@@ -107,5 +107,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.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
 maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
-maunium.net/go/mautrix v0.11.1-0.20220521221850-b3037c19004a h1:eaC/oCwiQl0G/ybPEWpzel0jwB/FKSsw86POz4dw3ss=
-maunium.net/go/mautrix v0.11.1-0.20220521221850-b3037c19004a/go.mod h1:oma8o6Y/5jcViBlDbX7tp1ajP2XP+b78h8twdI+zKI0=
+maunium.net/go/mautrix v0.11.1-0.20220522131515-87f89ec33247 h1:Hi90NviwsdYIY9/N6S4uZQuqPuHf/a2jYud4kC9tTcc=
+maunium.net/go/mautrix v0.11.1-0.20220522131515-87f89ec33247/go.mod h1:oma8o6Y/5jcViBlDbX7tp1ajP2XP+b78h8twdI+zKI0=

+ 15 - 10
main.go

@@ -33,6 +33,8 @@ import (
 	"google.golang.org/protobuf/proto"
 
 	"maunium.net/go/mautrix/bridge"
+	"maunium.net/go/mautrix/bridge/commands"
+	"maunium.net/go/mautrix/event"
 	"maunium.net/go/mautrix/id"
 	"maunium.net/go/mautrix/util/configupgrade"
 
@@ -53,14 +55,13 @@ var ExampleConfig string
 
 type WABridge struct {
 	bridge.Bridge
-	MatrixHandler *MatrixHandler
-	Config        *config.Config
-	DB            *database.Database
-	Provisioning  *ProvisioningAPI
-	Formatter     *Formatter
-	Metrics       *MetricsHandler
-	WAContainer   *sqlstore.Container
-	WAVersion     string
+	Config       *config.Config
+	DB           *database.Database
+	Provisioning *ProvisioningAPI
+	Formatter    *Formatter
+	Metrics      *MetricsHandler
+	WAContainer  *sqlstore.Container
+	WAVersion    string
 
 	usersByMXID         map[id.UserID]*User
 	usersByUsername     map[string]*User
@@ -78,6 +79,12 @@ type WABridge struct {
 }
 
 func (br *WABridge) Init() {
+	br.CommandProcessor = commands.NewProcessor(&br.Bridge)
+	br.RegisterCommands()
+
+	// TODO this is a weird place for this
+	br.EventProcessor.On(event.EphemeralEventPresence, br.HandlePresence)
+
 	Segment.log = br.Log.Sub("Segment")
 	Segment.key = br.Config.SegmentKey
 	if Segment.IsEnabled() {
@@ -93,8 +100,6 @@ func (br *WABridge) Init() {
 		br.Provisioning = &ProvisioningAPI{bridge: br}
 	}
 
-	br.Log.Debugln("Initializing Matrix event handler")
-	br.MatrixHandler = NewMatrixHandler(br)
 	br.Formatter = NewFormatter(br)
 	br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB)
 

+ 20 - 476
matrix.go

@@ -1,5 +1,5 @@
 // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
-// Copyright (C) 2021 Tulir Asokan
+// Copyright (C) 2022 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
@@ -17,17 +17,11 @@
 package main
 
 import (
-	"errors"
 	"fmt"
-	"strings"
-	"time"
-
-	"maunium.net/go/maulogger/v2"
 
 	"go.mau.fi/whatsmeow/types"
 
 	"maunium.net/go/mautrix"
-	"maunium.net/go/mautrix/appservice"
 	"maunium.net/go/mautrix/bridge"
 	"maunium.net/go/mautrix/event"
 	"maunium.net/go/mautrix/format"
@@ -36,149 +30,32 @@ import (
 	"maunium.net/go/mautrix-whatsapp/database"
 )
 
-type MatrixHandler struct {
-	bridge *WABridge
-	as     *appservice.AppService
-	log    maulogger.Logger
-	cmd    *CommandHandler
-}
-
-func NewMatrixHandler(bridge *WABridge) *MatrixHandler {
-	handler := &MatrixHandler{
-		bridge: bridge,
-		as:     bridge.AS,
-		log:    bridge.Log.Sub("Matrix"),
-		cmd:    NewCommandHandler(bridge),
-	}
-	bridge.EventProcessor.On(event.EventMessage, handler.HandleMessage)
-	bridge.EventProcessor.On(event.EventEncrypted, handler.HandleEncrypted)
-	bridge.EventProcessor.On(event.EventSticker, handler.HandleMessage)
-	bridge.EventProcessor.On(event.EventReaction, handler.HandleReaction)
-	bridge.EventProcessor.On(event.EventRedaction, handler.HandleRedaction)
-	bridge.EventProcessor.On(event.StateMember, handler.HandleMembership)
-	bridge.EventProcessor.On(event.StateRoomName, handler.HandleRoomMetadata)
-	bridge.EventProcessor.On(event.StateRoomAvatar, handler.HandleRoomMetadata)
-	bridge.EventProcessor.On(event.StateTopic, handler.HandleRoomMetadata)
-	bridge.EventProcessor.On(event.StateEncryption, handler.HandleEncryption)
-	bridge.EventProcessor.On(event.EphemeralEventPresence, handler.HandlePresence)
-	bridge.EventProcessor.On(event.EphemeralEventReceipt, handler.HandleReceipt)
-	bridge.EventProcessor.On(event.EphemeralEventTyping, handler.HandleTyping)
-	return handler
-}
-
-func (mx *MatrixHandler) HandleEncryption(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-	if evt.Content.AsEncryption().Algorithm != id.AlgorithmMegolmV1 {
-		return
-	}
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal != nil && !portal.Encrypted {
-		mx.log.Debugfln("%s enabled encryption in %s", evt.Sender, evt.RoomID)
-		portal.Encrypted = true
-		portal.Update(nil)
-		if portal.IsPrivateChat() {
-			err := mx.as.BotIntent().EnsureJoined(portal.MXID, appservice.EnsureJoinedParams{BotOverride: portal.MainIntent().Client})
-			if err != nil {
-				mx.log.Errorfln("Failed to join bot to %s after encryption was enabled: %v", evt.RoomID, err)
-			}
-		}
-	}
-}
-
-func (mx *MatrixHandler) joinAndCheckMembers(evt *event.Event, intent *appservice.IntentAPI) *mautrix.RespJoinedMembers {
-	resp, err := intent.JoinRoomByID(evt.RoomID)
-	if err != nil {
-		mx.log.Debugfln("Failed to join room %s as %s with invite from %s: %v", evt.RoomID, intent.UserID, evt.Sender, err)
-		return nil
-	}
-
-	members, err := intent.JoinedMembers(resp.RoomID)
-	if err != nil {
-		mx.log.Debugfln("Failed to get members in room %s after accepting invite from %s as %s: %v", resp.RoomID, evt.Sender, intent.UserID, err)
-		_, _ = intent.LeaveRoom(resp.RoomID)
-		return nil
-	}
-
-	if len(members.Joined) < 2 {
-		mx.log.Debugln("Leaving empty room", resp.RoomID, "after accepting invite from", evt.Sender, "as", intent.UserID)
-		_, _ = intent.LeaveRoom(resp.RoomID)
-		return nil
-	}
-	return members
-}
-
-func (mx *MatrixHandler) sendNoticeWithMarkdown(roomID id.RoomID, message string) (*mautrix.RespSendEvent, error) {
-	intent := mx.as.BotIntent()
-	content := format.RenderMarkdown(message, true, false)
-	content.MsgType = event.MsgNotice
-	return intent.SendMessageEvent(roomID, event.EventMessage, content)
-}
-
-func (mx *MatrixHandler) HandleBotInvite(evt *event.Event) {
-	intent := mx.as.BotIntent()
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil {
-		return
-	}
-
-	members := mx.joinAndCheckMembers(evt, intent)
-	if members == nil {
-		return
-	}
-
-	if !user.Whitelisted {
-		_, _ = intent.SendNotice(evt.RoomID, "You are not whitelisted to use this bridge.\n"+
-			"If you're the owner of this bridge, see the bridge.permissions section in your config file.")
-		_, _ = intent.LeaveRoom(evt.RoomID)
-		return
-	}
-
-	_, _ = mx.sendNoticeWithMarkdown(evt.RoomID, mx.bridge.Config.Bridge.ManagementRoomText.Welcome)
-
-	if len(members.Joined) == 2 && (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.")
-		mx.log.Debugln(evt.RoomID, "registered as a management room with", evt.Sender)
-	}
-
-	if evt.RoomID == user.ManagementRoom {
-		if user.HasSession() {
-			_, _ = mx.sendNoticeWithMarkdown(evt.RoomID, mx.bridge.Config.Bridge.ManagementRoomText.WelcomeConnected)
-		} else {
-			_, _ = mx.sendNoticeWithMarkdown(evt.RoomID, mx.bridge.Config.Bridge.ManagementRoomText.WelcomeUnconnected)
-		}
-
-		additionalHelp := mx.bridge.Config.Bridge.ManagementRoomText.AdditionalHelp
-		if len(additionalHelp) > 0 {
-			_, _ = mx.sendNoticeWithMarkdown(evt.RoomID, additionalHelp)
-		}
-	}
-}
-
-func (mx *MatrixHandler) handlePrivatePortal(roomID id.RoomID, inviter *User, puppet *Puppet, key database.PortalKey) {
-	portal := mx.bridge.GetPortalByJID(key)
+func (br *WABridge) CreatePrivatePortal(roomID id.RoomID, brInviter bridge.User, brGhost bridge.Ghost) {
+	inviter := brInviter.(*User)
+	puppet := brGhost.(*Puppet)
+	key := database.NewPortalKey(puppet.JID, inviter.JID)
+	portal := br.GetPortalByJID(key)
 
 	if len(portal.MXID) == 0 {
-		mx.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
+		br.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(roomID, inviter, puppet, portal)
+		br.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)
+		br.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
 		return
 	}
 	intent := puppet.DefaultIntent()
 	errorMessage := fmt.Sprintf("You already have a private chat portal with me at [%[1]s](https://matrix.to/#/%[1]s)", portal.MXID)
 	errorContent := format.RenderMarkdown(errorMessage, true, false)
 	_, _ = intent.SendMessageEvent(roomID, event.EventMessage, errorContent)
-	mx.log.Debugfln("Leaving private chat room %s as %s after accepting invite from %s as we already have chat with the user", roomID, puppet.MXID, inviter.MXID)
+	br.Log.Debugfln("Leaving private chat room %s as %s after accepting invite from %s as we already have chat with the user", roomID, puppet.MXID, inviter.MXID)
 	_, _ = intent.LeaveRoom(roomID)
 }
 
-func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter *User, puppet *Puppet, portal *Portal) {
+func (br *WABridge) createPrivatePortalFromInvite(roomID id.RoomID, inviter *User, puppet *Puppet, portal *Portal) {
 	portal.MXID = roomID
 	portal.Topic = PrivateChatTopic
 	_, _ = portal.MainIntent().SetRoomTopic(portal.MXID, portal.Topic)
@@ -194,12 +71,12 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter
 	portal.log.Infofln("Created private chat portal in %s after invite from %s", roomID, inviter.MXID)
 	intent := puppet.DefaultIntent()
 
-	if mx.bridge.Config.Bridge.Encryption.Default {
-		_, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{UserID: mx.bridge.Bot.UserID})
+	if br.Config.Bridge.Encryption.Default {
+		_, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{UserID: br.Bot.UserID})
 		if err != nil {
 			portal.log.Warnln("Failed to invite bridge bot to enable e2be:", err)
 		}
-		err = mx.bridge.Bot.EnsureJoined(roomID)
+		err = br.Bot.EnsureJoined(roomID)
 		if err != nil {
 			portal.log.Warnln("Failed to join as bridge bot to enable e2be:", err)
 		}
@@ -207,9 +84,9 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter
 		if err != nil {
 			portal.log.Warnln("Failed to enable e2be:", err)
 		}
-		mx.as.StateStore.SetMembership(roomID, inviter.MXID, event.MembershipJoin)
-		mx.as.StateStore.SetMembership(roomID, puppet.MXID, event.MembershipJoin)
-		mx.as.StateStore.SetMembership(roomID, mx.bridge.Bot.UserID, event.MembershipJoin)
+		br.AS.StateStore.SetMembership(roomID, inviter.MXID, event.MembershipJoin)
+		br.AS.StateStore.SetMembership(roomID, puppet.MXID, event.MembershipJoin)
+		br.AS.StateStore.SetMembership(roomID, br.Bot.UserID, event.MembershipJoin)
 		portal.Encrypted = true
 	}
 	portal.Update(nil)
@@ -217,312 +94,12 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter
 	_, _ = intent.SendNotice(roomID, "Private chat portal created")
 }
 
-func (mx *MatrixHandler) HandlePuppetInvite(evt *event.Event, inviter *User, puppet *Puppet) {
-	intent := puppet.DefaultIntent()
-
-	if !inviter.Whitelisted {
-		puppet.log.Debugfln("Rejecting invite from %s to %s: user is not whitelisted", evt.Sender, evt.RoomID)
-		_, err := intent.LeaveRoom(evt.RoomID, &mautrix.ReqLeave{
-			Reason: "You're not whitelisted to use this bridge",
-		})
-		if err != nil {
-			puppet.log.Warnfln("Failed to reject invite from %s to %s: %v", evt.Sender, evt.RoomID, err)
-		}
-		return
-	} else if !inviter.IsLoggedIn() {
-		puppet.log.Debugfln("Rejecting invite from %s to %s: user is not logged in", evt.Sender, evt.RoomID)
-		_, err := intent.LeaveRoom(evt.RoomID, &mautrix.ReqLeave{
-			Reason: "You're not logged into this bridge",
-		})
-		if err != nil {
-			puppet.log.Warnfln("Failed to reject invite from %s to %s: %v", evt.Sender, evt.RoomID, err)
-		}
-		return
-	}
-
-	members := mx.joinAndCheckMembers(evt, intent)
-	if members == nil {
-		return
-	}
-	var hasBridgeBot, hasOtherUsers bool
-	for mxid, _ := range members.Joined {
-		if mxid == intent.UserID || mxid == inviter.MXID {
-			continue
-		} else if mxid == mx.bridge.Bot.UserID {
-			hasBridgeBot = true
-		} else {
-			hasOtherUsers = true
-		}
-	}
-	if !hasBridgeBot && !hasOtherUsers {
-		key := database.NewPortalKey(puppet.JID, inviter.JID)
-		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.")
-		_, _ = intent.LeaveRoom(evt.RoomID)
-	} else {
-		_, _ = intent.SendNotice(evt.RoomID, "This puppet will remain inactive until this room is bridged to a WhatsApp group.")
-	}
-}
-
-func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
-	if _, isPuppet := mx.bridge.ParsePuppetMXID(evt.Sender); evt.Sender == mx.bridge.Bot.UserID || isPuppet {
-		return
-	}
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-
-	if mx.bridge.Crypto != nil {
-		mx.bridge.Crypto.HandleMemberEvent(evt)
-	}
-
-	content := evt.Content.AsMember()
-	if content.Membership == event.MembershipInvite && id.UserID(evt.GetStateKey()) == mx.as.BotMXID() {
-		mx.HandleBotInvite(evt)
-		return
-	}
-
-	if mx.shouldIgnoreEvent(evt) {
-		return
-	}
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil {
-		return
-	}
-	isSelf := id.UserID(evt.GetStateKey()) == evt.Sender
-	puppet := mx.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey()))
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal == nil {
-		if puppet != nil && content.Membership == event.MembershipInvite {
-			mx.HandlePuppetInvite(evt, user, puppet)
-		}
-		return
-	} else if !user.Whitelisted || !user.IsLoggedIn() {
-		return
-	}
-
-	if content.Membership == event.MembershipLeave {
-		if evt.Unsigned.PrevContent != nil {
-			_ = evt.Unsigned.PrevContent.ParseRaw(evt.Type)
-			prevContent, ok := evt.Unsigned.PrevContent.Parsed.(*event.MemberEventContent)
-			if ok && prevContent.Membership != "join" {
-				return
-			}
-		}
-		if isSelf {
-			portal.HandleMatrixLeave(user)
-		} else if puppet != nil {
-			portal.HandleMatrixKick(user, puppet)
-		}
-	} else if content.Membership == event.MembershipInvite && !isSelf && puppet != nil {
-		portal.HandleMatrixInvite(user, puppet)
-	}
-}
-
-func (mx *MatrixHandler) HandleRoomMetadata(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-	if mx.shouldIgnoreEvent(evt) {
-		return
-	}
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil || !user.Whitelisted || !user.IsLoggedIn() {
-		return
-	}
-
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal == nil || portal.IsPrivateChat() {
-		return
-	}
-
-	portal.HandleMatrixMeta(user, evt)
-}
-
-func (mx *MatrixHandler) shouldIgnoreEvent(evt *event.Event) bool {
-	if _, isPuppet := mx.bridge.ParsePuppetMXID(evt.Sender); evt.Sender == mx.bridge.Bot.UserID || isPuppet {
-		return true
-	}
-	if val, ok := evt.Content.Raw[doublePuppetKey]; ok && val == doublePuppetValue && mx.bridge.GetPuppetByCustomMXID(evt.Sender) != nil {
-		return true
-	}
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if !user.RelayWhitelisted {
-		return true
-	}
-	return false
-}
-
-const sessionWaitTimeout = 5 * time.Second
-
-func (mx *MatrixHandler) HandleEncrypted(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-	if mx.shouldIgnoreEvent(evt) || mx.bridge.Crypto == nil {
-		return
-	}
-
-	decrypted, err := mx.bridge.Crypto.Decrypt(evt)
-	decryptionRetryCount := 0
-	if errors.Is(err, bridge.NoSessionFound) {
-		content := evt.Content.AsEncrypted()
-		mx.log.Debugfln("Couldn't find session %s trying to decrypt %s, waiting %d seconds...", content.SessionID, evt.ID, int(sessionWaitTimeout.Seconds()))
-		mx.as.SendErrorMessageSendCheckpoint(evt, appservice.StepDecrypted, err, false, decryptionRetryCount)
-		decryptionRetryCount++
-		if mx.bridge.Crypto.WaitForSession(evt.RoomID, content.SenderKey, content.SessionID, sessionWaitTimeout) {
-			mx.log.Debugfln("Got session %s after waiting, trying to decrypt %s again", content.SessionID, evt.ID)
-			decrypted, err = mx.bridge.Crypto.Decrypt(evt)
-		} else {
-			mx.as.SendErrorMessageSendCheckpoint(evt, appservice.StepDecrypted, fmt.Errorf("didn't receive encryption keys"), false, decryptionRetryCount)
-			go mx.waitLongerForSession(evt)
-			return
-		}
-	}
-	if err != nil {
-		mx.as.SendErrorMessageSendCheckpoint(evt, appservice.StepDecrypted, err, true, decryptionRetryCount)
-
-		mx.log.Warnfln("Failed to decrypt %s: %v", evt.ID, err)
-		_, _ = mx.bridge.Bot.SendNotice(evt.RoomID, fmt.Sprintf(
-			"\u26a0 Your message was not bridged: %v", err))
-		return
-	}
-	mx.as.SendMessageSendCheckpoint(decrypted, appservice.StepDecrypted, decryptionRetryCount)
-	mx.bridge.EventProcessor.Dispatch(decrypted)
-}
-
-func (mx *MatrixHandler) waitLongerForSession(evt *event.Event) {
-	const extendedTimeout = sessionWaitTimeout * 3
-
-	content := evt.Content.AsEncrypted()
-	mx.log.Debugfln("Couldn't find session %s trying to decrypt %s, waiting %d more seconds...",
-		content.SessionID, evt.ID, int(extendedTimeout.Seconds()))
-
-	go mx.bridge.Crypto.RequestSession(evt.RoomID, content.SenderKey, content.SessionID, evt.Sender, content.DeviceID)
-
-	resp, err := mx.bridge.Bot.SendNotice(evt.RoomID, fmt.Sprintf(
-		"\u26a0 Your message was not bridged: the bridge hasn't received the decryption keys. "+
-			"The bridge will retry for %d seconds. If this error keeps happening, try restarting your client.",
-		int(extendedTimeout.Seconds())))
-	if err != nil {
-		mx.log.Errorfln("Failed to send decryption error to %s: %v", evt.RoomID, err)
-	}
-	update := event.MessageEventContent{MsgType: event.MsgNotice}
-
-	if mx.bridge.Crypto.WaitForSession(evt.RoomID, content.SenderKey, content.SessionID, extendedTimeout) {
-		mx.log.Debugfln("Got session %s after waiting more, trying to decrypt %s again", content.SessionID, evt.ID)
-		decrypted, err := mx.bridge.Crypto.Decrypt(evt)
-		if err == nil {
-			mx.as.SendMessageSendCheckpoint(decrypted, appservice.StepDecrypted, 2)
-			mx.bridge.EventProcessor.Dispatch(decrypted)
-			_, _ = mx.bridge.Bot.RedactEvent(evt.RoomID, resp.EventID)
-			return
-		}
-		mx.log.Warnfln("Failed to decrypt %s: %v", evt.ID, err)
-		mx.as.SendErrorMessageSendCheckpoint(evt, appservice.StepDecrypted, err, true, 2)
-		update.Body = fmt.Sprintf("\u26a0 Your message was not bridged: %v", err)
-	} else {
-		mx.log.Debugfln("Didn't get %s, giving up on %s", content.SessionID, evt.ID)
-		mx.as.SendErrorMessageSendCheckpoint(evt, appservice.StepDecrypted, fmt.Errorf("didn't receive encryption keys"), true, 2)
-		update.Body = "\u26a0 Your message was not bridged: the bridge hasn't received the decryption keys. " +
-			"If this error keeps happening, try restarting your client."
-	}
-
-	newContent := update
-	update.NewContent = &newContent
-	if resp != nil {
-		update.RelatesTo = &event.RelatesTo{
-			Type:    event.RelReplace,
-			EventID: resp.EventID,
-		}
-	}
-	_, err = mx.bridge.Bot.SendMessageEvent(evt.RoomID, event.EventMessage, &update)
-	if err != nil {
-		mx.log.Debugfln("Failed to update decryption error notice %s: %v", resp.EventID, err)
-	}
-}
-
-func (mx *MatrixHandler) HandleMessage(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-	if mx.shouldIgnoreEvent(evt) {
-		return
-	}
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil {
-		return
-	}
-
-	content := evt.Content.AsMessage()
-	content.RemoveReplyFallback()
-	if user.Whitelisted && content.MsgType == event.MsgText {
-		commandPrefix := mx.bridge.Config.Bridge.CommandPrefix
-		hasCommandPrefix := strings.HasPrefix(content.Body, commandPrefix)
-		if hasCommandPrefix {
-			content.Body = strings.TrimLeft(content.Body[len(commandPrefix):], " ")
-		}
-		if hasCommandPrefix || evt.RoomID == user.ManagementRoom {
-			mx.cmd.Handle(evt.RoomID, evt.ID, user, content.Body, content.GetReplyTo())
-			return
-		}
-	}
-
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal != nil && (user.Whitelisted || portal.HasRelaybot()) {
-		portal.matrixMessages <- PortalMatrixMessage{user: user, evt: evt}
-	}
-}
-
-func (mx *MatrixHandler) HandleReaction(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-	if mx.shouldIgnoreEvent(evt) {
-		return
-	}
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil || !user.Whitelisted || !user.IsLoggedIn() {
-		return
-	}
-
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal == nil {
-		return
-	} else if portal.IsPrivateChat() && user.JID.User != portal.Key.Receiver.User {
-		// One user can only react once, so we don't use the relay user for reactions
-		return
-	}
-
-	content := evt.Content.AsReaction()
-	if strings.Contains(content.RelatesTo.Key, "retry") || strings.HasPrefix(content.RelatesTo.Key, "\u267b") { // ♻️
-		if retryRequested, _ := portal.requestMediaRetry(user, content.RelatesTo.EventID, nil); retryRequested {
-			_, _ = portal.MainIntent().RedactEvent(portal.MXID, evt.ID, mautrix.ReqRedact{
-				Reason: "requested media from phone",
-			})
-			// Errored media, don't try to send as reaction
-			return
-		}
-	}
-	portal.HandleMatrixReaction(user, evt)
-}
-
-func (mx *MatrixHandler) HandleRedaction(evt *event.Event) {
-	defer mx.bridge.Metrics.TrackMatrixEvent(evt.Type)()
-
-	user := mx.bridge.GetUserByMXID(evt.Sender)
-	if user == nil {
-		return
-	}
-
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal != nil && (user.Whitelisted || portal.HasRelaybot()) {
-		portal.matrixMessages <- PortalMatrixMessage{user: user, evt: evt}
-	}
-}
-
-func (mx *MatrixHandler) HandlePresence(evt *event.Event) {
-	user := mx.bridge.GetUserByMXIDIfExists(evt.Sender)
+func (br *WABridge) HandlePresence(evt *event.Event) {
+	user := br.GetUserByMXIDIfExists(evt.Sender)
 	if user == nil || !user.IsLoggedIn() {
 		return
 	}
-	customPuppet := mx.bridge.GetPuppetByCustomMXID(user.MXID)
+	customPuppet := br.GetPuppetByCustomMXID(user.MXID)
 	// TODO move this flag to the user and/or portal data
 	if customPuppet != nil && !customPuppet.EnablePresence {
 		return
@@ -543,36 +120,3 @@ func (mx *MatrixHandler) HandlePresence(evt *event.Event) {
 		}
 	}
 }
-
-func (mx *MatrixHandler) HandleReceipt(evt *event.Event) {
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal == nil {
-		return
-	}
-
-	for eventID, receipts := range *evt.Content.AsReceipt() {
-		for userID, receipt := range receipts.Read {
-			if user := mx.bridge.GetUserByMXIDIfExists(userID); user == nil {
-				// Not a bridge user
-			} else if customPuppet := mx.bridge.GetPuppetByCustomMXID(user.MXID); customPuppet != nil && !customPuppet.EnableReceipts {
-				// TODO move this flag to the user and/or portal data
-				continue
-			} else if val, ok := receipt.Extra[doublePuppetKey].(string); ok && customPuppet != nil && val == doublePuppetValue {
-				// Ignore double puppeted read receipts.
-				user.log.Debugfln("Ignoring double puppeted read receipt %+v", evt.Content.Raw)
-				// But do start disappearing messages, because the user read the chat
-				portal.ScheduleDisappearing()
-			} else {
-				portal.HandleMatrixReadReceipt(user, eventID, time.UnixMilli(receipt.Timestamp), true)
-			}
-		}
-	}
-}
-
-func (mx *MatrixHandler) HandleTyping(evt *event.Event) {
-	portal := mx.bridge.GetPortalByMXID(evt.RoomID)
-	if portal == nil {
-		return
-	}
-	portal.HandleMatrixTyping(evt.Content.AsTyping().UserIDs)
-}

+ 58 - 12
portal.go

@@ -80,8 +80,27 @@ func (br *WABridge) GetPortalByMXID(mxid id.RoomID) *Portal {
 	return portal
 }
 
-func (br *WABridge) GetIPortalByMXID(mxid id.RoomID) bridge.Portal {
-	return br.GetPortalByMXID(mxid)
+func (br *WABridge) GetIPortal(mxid id.RoomID) bridge.Portal {
+	p := br.GetPortalByMXID(mxid)
+	if p == nil {
+		return nil
+	}
+	return p
+}
+
+func (portal *Portal) IsEncrypted() bool {
+	return portal.Encrypted
+}
+
+func (portal *Portal) MarkEncrypted() {
+	portal.Encrypted = true
+	portal.Update(nil)
+}
+
+func (portal *Portal) ReceiveMatrixEvent(user bridge.User, evt *event.Event) {
+	if user.GetPermissionLevel() >= bridge.PermissionUser || portal.HasRelaybot() {
+		portal.matrixMessages <- PortalMatrixMessage{user: user.(*User), evt: evt}
+	}
 }
 
 func (br *WABridge) GetPortalByJID(key database.PortalKey) *Portal {
@@ -234,10 +253,6 @@ type Portal struct {
 	relayUser *User
 }
 
-func (portal *Portal) IsEncrypted() bool {
-	return portal.Encrypted
-}
-
 func (portal *Portal) handleMessageLoopItem(msg PortalMessage) {
 	if len(portal.MXID) == 0 {
 		if msg.fake == nil && msg.undecryptable == nil && (msg.evt == nil || !containsSupportedMessage(msg.evt.Message)) {
@@ -266,12 +281,14 @@ func (portal *Portal) handleMessageLoopItem(msg PortalMessage) {
 }
 
 func (portal *Portal) handleMatrixMessageLoopItem(msg PortalMatrixMessage) {
-	portal.HandleMatrixReadReceipt(msg.user, "", time.UnixMilli(msg.evt.Timestamp), false)
+	portal.handleMatrixReadReceipt(msg.user, "", time.UnixMilli(msg.evt.Timestamp), false)
 	switch msg.evt.Type {
 	case event.EventMessage, event.EventSticker:
 		portal.HandleMatrixMessage(msg.user, msg.evt)
 	case event.EventRedaction:
 		portal.HandleMatrixRedaction(msg.user, msg.evt)
+	case event.EventReaction:
+		portal.HandleMatrixReaction(msg.user, msg.evt)
 	default:
 		portal.log.Warnln("Unsupported event type %+v in portal message channel", msg.evt.Type)
 	}
@@ -2897,6 +2914,21 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
 }
 
 func (portal *Portal) HandleMatrixReaction(sender *User, evt *event.Event) {
+	if portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User {
+		return
+	}
+
+	content, ok := evt.Content.Parsed.(*event.ReactionEventContent)
+	if ok && strings.Contains(content.RelatesTo.Key, "retry") || strings.HasPrefix(content.RelatesTo.Key, "\u267b") { // ♻️
+		if retryRequested, _ := portal.requestMediaRetry(sender, content.RelatesTo.EventID, nil); retryRequested {
+			_, _ = portal.MainIntent().RedactEvent(portal.MXID, evt.ID, mautrix.ReqRedact{
+				Reason: "requested media from phone",
+			})
+			// Errored media, don't try to send as reaction
+			return
+		}
+	}
+
 	portal.log.Debugfln("Received reaction event %s from %s", evt.ID, evt.Sender)
 	err := portal.handleMatrixReaction(sender, evt)
 	if err != nil {
@@ -3033,7 +3065,11 @@ func (portal *Portal) HandleMatrixRedaction(sender *User, evt *event.Event) {
 	}
 }
 
-func (portal *Portal) HandleMatrixReadReceipt(sender *User, eventID id.EventID, receiptTimestamp time.Time, isExplicit bool) {
+func (portal *Portal) HandleMatrixReadReceipt(sender bridge.User, eventID id.EventID, receiptTimestamp time.Time) {
+	portal.handleMatrixReadReceipt(sender.(*User), eventID, receiptTimestamp, true)
+}
+
+func (portal *Portal) handleMatrixReadReceipt(sender *User, eventID id.EventID, receiptTimestamp time.Time, isExplicit bool) {
 	if !sender.IsLoggedIn() {
 		if isExplicit {
 			portal.log.Debugfln("Ignoring read receipt by %s: user is not connected to WhatsApp", sender.JID)
@@ -3247,7 +3283,8 @@ func (portal *Portal) Cleanup(puppetsOnly bool) {
 	}
 }
 
-func (portal *Portal) HandleMatrixLeave(sender *User) {
+func (portal *Portal) HandleMatrixLeave(brSender bridge.User) {
+	sender := brSender.(*User)
 	if portal.IsPrivateChat() {
 		portal.log.Debugln("User left private chat portal, cleaning up and deleting...")
 		portal.Delete()
@@ -3264,7 +3301,9 @@ func (portal *Portal) HandleMatrixLeave(sender *User) {
 	portal.CleanupIfEmpty()
 }
 
-func (portal *Portal) HandleMatrixKick(sender *User, target *Puppet) {
+func (portal *Portal) HandleMatrixKick(brSender bridge.User, brTarget bridge.Ghost) {
+	sender := brSender.(*User)
+	target := brTarget.(*Puppet)
 	_, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
 		target.JID: whatsmeow.ParticipantChangeRemove,
 	})
@@ -3275,7 +3314,9 @@ func (portal *Portal) HandleMatrixKick(sender *User, target *Puppet) {
 	//portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp)
 }
 
-func (portal *Portal) HandleMatrixInvite(sender *User, target *Puppet) {
+func (portal *Portal) HandleMatrixInvite(brSender bridge.User, brTarget bridge.Ghost) {
+	sender := brSender.(*User)
+	target := brTarget.(*Puppet)
 	_, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
 		target.JID: whatsmeow.ParticipantChangeAdd,
 	})
@@ -3286,7 +3327,12 @@ func (portal *Portal) HandleMatrixInvite(sender *User, target *Puppet) {
 	//portal.log.Infofln("Add %s response: %s", puppet.JID, <-resp)
 }
 
-func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
+func (portal *Portal) HandleMatrixMeta(brSender bridge.User, evt *event.Event) {
+	sender := brSender.(*User)
+	if !sender.Whitelisted || !sender.IsLoggedIn() {
+		return
+	}
+
 	switch content := evt.Content.Parsed.(type) {
 	case *event.RoomNameEventContent:
 		if content.Name == portal.Name {

+ 26 - 0
puppet.go

@@ -31,6 +31,7 @@ import (
 	log "maunium.net/go/maulogger/v2"
 
 	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/bridge"
 	"maunium.net/go/mautrix/id"
 
 	"maunium.net/go/mautrix-whatsapp/config"
@@ -104,6 +105,31 @@ func (br *WABridge) GetPuppetByCustomMXID(mxid id.UserID) *Puppet {
 	return puppet
 }
 
+func (user *User) GetIDoublePuppet() bridge.DoublePuppet {
+	p := user.bridge.GetPuppetByCustomMXID(user.MXID)
+	if p == nil {
+		return nil
+	}
+	return p
+}
+
+func (br *WABridge) IsGhost(id id.UserID) bool {
+	_, ok := br.ParsePuppetMXID(id)
+	return ok
+}
+
+func (br *WABridge) GetIGhost(id id.UserID) bridge.Ghost {
+	p := br.GetPuppetByMXID(id)
+	if p == nil {
+		return nil
+	}
+	return p
+}
+
+func (p *Puppet) GetMXID() id.UserID {
+	return p.MXID
+}
+
 func (br *WABridge) GetAllPuppetsWithCustomMXID() []*Puppet {
 	return br.dbPuppetsToPuppets(br.DB.Puppet.GetAllWithCustomMXID())
 }

+ 23 - 4
user.go

@@ -63,6 +63,7 @@ type User struct {
 	Admin            bool
 	Whitelisted      bool
 	RelayWhitelisted bool
+	PermissionLevel  bridge.PermissionLevel
 
 	mgmtCreateLock  sync.Mutex
 	spaceCreateLock sync.Mutex
@@ -107,12 +108,28 @@ func (br *WABridge) GetUserByMXID(userID id.UserID) *User {
 	return br.getUserByMXID(userID, false)
 }
 
-func (br *WABridge) GetIUserByMXID(userID id.UserID) bridge.User {
-	return br.getUserByMXID(userID, false)
+func (br *WABridge) GetIUser(userID id.UserID, create bool) bridge.User {
+	u := br.getUserByMXID(userID, !create)
+	if u == nil {
+		return nil
+	}
+	return u
+}
+
+func (user *User) GetPermissionLevel() bridge.PermissionLevel {
+	return user.PermissionLevel
+}
+
+func (user *User) GetManagementRoomID() id.RoomID {
+	return user.ManagementRoom
+}
+
+func (user *User) GetMXID() id.UserID {
+	return user.MXID
 }
 
-func (user *User) IsAdmin() bool {
-	return user.Admin
+func (user *User) GetCommandState() map[string]interface{} {
+	return nil
 }
 
 func (br *WABridge) GetUserByMXIDIfExists(userID id.UserID) *User {
@@ -201,9 +218,11 @@ func (br *WABridge) NewUser(dbUser *database.User) *User {
 		historySyncs: make(chan *events.HistorySync, 32),
 		lastPresence: types.PresenceUnavailable,
 	}
+
 	user.RelayWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelayWhitelisted(user.MXID)
 	user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
 	user.Admin = user.bridge.Config.Bridge.Permissions.IsAdmin(user.MXID)
+	user.PermissionLevel = bridge.PermissionLevel(user.bridge.Config.Bridge.Permissions.GetPermissionLevel(user.MXID))
 	if len(user.bridge.Config.Homeserver.StatusEndpoint) > 0 {
 		user.bridgeStateQueue = make(chan BridgeState, 10)
 		go user.bridgeStateLoop()

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov