mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-15 01:43:49 +01:00
Move Matrix event and command handling to mautrix-go
This commit is contained in:
parent
a8c72a57f9
commit
73304cd400
10 changed files with 516 additions and 882 deletions
707
commands.go
707
commands.go
File diff suppressed because it is too large
Load diff
|
@ -112,12 +112,7 @@ type BridgeConfig struct {
|
||||||
|
|
||||||
CommandPrefix string `yaml:"command_prefix"`
|
CommandPrefix string `yaml:"command_prefix"`
|
||||||
|
|
||||||
ManagementRoomText struct {
|
ManagementRoomText bridgeconfig.ManagementRoomTexts `yaml:"management_room_text"`
|
||||||
Welcome string `yaml:"welcome"`
|
|
||||||
WelcomeConnected string `yaml:"welcome_connected"`
|
|
||||||
WelcomeUnconnected string `yaml:"welcome_unconnected"`
|
|
||||||
AdditionalHelp string `yaml:"additional_help"`
|
|
||||||
} `yaml:"management_room_text"`
|
|
||||||
|
|
||||||
Encryption bridgeconfig.EncryptionConfig `yaml:"encryption"`
|
Encryption bridgeconfig.EncryptionConfig `yaml:"encryption"`
|
||||||
|
|
||||||
|
@ -138,6 +133,14 @@ func (bc BridgeConfig) GetEncryptionConfig() bridgeconfig.EncryptionConfig {
|
||||||
return bc.Encryption
|
return bc.Encryption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc BridgeConfig) GetCommandPrefix() string {
|
||||||
|
return bc.CommandPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BridgeConfig) GetManagementRoomTexts() bridgeconfig.ManagementRoomTexts {
|
||||||
|
return bc.ManagementRoomText
|
||||||
|
}
|
||||||
|
|
||||||
type umBridgeConfig BridgeConfig
|
type umBridgeConfig BridgeConfig
|
||||||
|
|
||||||
func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
|
|
@ -219,7 +219,7 @@ func (puppet *Puppet) ProcessResponse(resp *mautrix.RespSync, _ string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go puppet.bridge.MatrixHandler.HandlePresence(evt)
|
go puppet.bridge.HandlePresence(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -15,7 +15,7 @@ require (
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
maunium.net/go/maulogger/v2 v2.3.2
|
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 (
|
require (
|
||||||
|
|
4
go.sum
4
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/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 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
|
||||||
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
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.20220522131515-87f89ec33247 h1:Hi90NviwsdYIY9/N6S4uZQuqPuHf/a2jYud4kC9tTcc=
|
||||||
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/go.mod h1:oma8o6Y/5jcViBlDbX7tp1ajP2XP+b78h8twdI+zKI0=
|
||||||
|
|
11
main.go
11
main.go
|
@ -33,6 +33,8 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/bridge"
|
"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/id"
|
||||||
"maunium.net/go/mautrix/util/configupgrade"
|
"maunium.net/go/mautrix/util/configupgrade"
|
||||||
|
|
||||||
|
@ -53,7 +55,6 @@ var ExampleConfig string
|
||||||
|
|
||||||
type WABridge struct {
|
type WABridge struct {
|
||||||
bridge.Bridge
|
bridge.Bridge
|
||||||
MatrixHandler *MatrixHandler
|
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
DB *database.Database
|
DB *database.Database
|
||||||
Provisioning *ProvisioningAPI
|
Provisioning *ProvisioningAPI
|
||||||
|
@ -78,6 +79,12 @@ type WABridge struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *WABridge) Init() {
|
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.log = br.Log.Sub("Segment")
|
||||||
Segment.key = br.Config.SegmentKey
|
Segment.key = br.Config.SegmentKey
|
||||||
if Segment.IsEnabled() {
|
if Segment.IsEnabled() {
|
||||||
|
@ -93,8 +100,6 @@ func (br *WABridge) Init() {
|
||||||
br.Provisioning = &ProvisioningAPI{bridge: br}
|
br.Provisioning = &ProvisioningAPI{bridge: br}
|
||||||
}
|
}
|
||||||
|
|
||||||
br.Log.Debugln("Initializing Matrix event handler")
|
|
||||||
br.MatrixHandler = NewMatrixHandler(br)
|
|
||||||
br.Formatter = NewFormatter(br)
|
br.Formatter = NewFormatter(br)
|
||||||
br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB)
|
br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB)
|
||||||
|
|
||||||
|
|
496
matrix.go
496
matrix.go
|
@ -1,5 +1,5 @@
|
||||||
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
// 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
|
// 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
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -17,17 +17,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"maunium.net/go/maulogger/v2"
|
|
||||||
|
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/appservice"
|
|
||||||
"maunium.net/go/mautrix/bridge"
|
"maunium.net/go/mautrix/bridge"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
|
@ -36,149 +30,32 @@ import (
|
||||||
"maunium.net/go/mautrix-whatsapp/database"
|
"maunium.net/go/mautrix-whatsapp/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MatrixHandler struct {
|
func (br *WABridge) CreatePrivatePortal(roomID id.RoomID, brInviter bridge.User, brGhost bridge.Ghost) {
|
||||||
bridge *WABridge
|
inviter := brInviter.(*User)
|
||||||
as *appservice.AppService
|
puppet := brGhost.(*Puppet)
|
||||||
log maulogger.Logger
|
key := database.NewPortalKey(puppet.JID, inviter.JID)
|
||||||
cmd *CommandHandler
|
portal := br.GetPortalByJID(key)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if len(portal.MXID) == 0 {
|
if len(portal.MXID) == 0 {
|
||||||
mx.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
|
br.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := portal.MainIntent().EnsureInvited(portal.MXID, inviter.MXID)
|
err := portal.MainIntent().EnsureInvited(portal.MXID, inviter.MXID)
|
||||||
if err != nil {
|
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)
|
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)
|
||||||
mx.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
|
br.createPrivatePortalFromInvite(roomID, inviter, puppet, portal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
intent := puppet.DefaultIntent()
|
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)
|
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)
|
errorContent := format.RenderMarkdown(errorMessage, true, false)
|
||||||
_, _ = intent.SendMessageEvent(roomID, event.EventMessage, errorContent)
|
_, _ = 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)
|
_, _ = 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.MXID = roomID
|
||||||
portal.Topic = PrivateChatTopic
|
portal.Topic = PrivateChatTopic
|
||||||
_, _ = portal.MainIntent().SetRoomTopic(portal.MXID, portal.Topic)
|
_, _ = 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)
|
portal.log.Infofln("Created private chat portal in %s after invite from %s", roomID, inviter.MXID)
|
||||||
intent := puppet.DefaultIntent()
|
intent := puppet.DefaultIntent()
|
||||||
|
|
||||||
if mx.bridge.Config.Bridge.Encryption.Default {
|
if br.Config.Bridge.Encryption.Default {
|
||||||
_, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{UserID: mx.bridge.Bot.UserID})
|
_, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{UserID: br.Bot.UserID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Warnln("Failed to invite bridge bot to enable e2be:", err)
|
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 {
|
if err != nil {
|
||||||
portal.log.Warnln("Failed to join as bridge bot to enable e2be:", err)
|
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 {
|
if err != nil {
|
||||||
portal.log.Warnln("Failed to enable e2be:", err)
|
portal.log.Warnln("Failed to enable e2be:", err)
|
||||||
}
|
}
|
||||||
mx.as.StateStore.SetMembership(roomID, inviter.MXID, event.MembershipJoin)
|
br.AS.StateStore.SetMembership(roomID, inviter.MXID, event.MembershipJoin)
|
||||||
mx.as.StateStore.SetMembership(roomID, puppet.MXID, event.MembershipJoin)
|
br.AS.StateStore.SetMembership(roomID, puppet.MXID, event.MembershipJoin)
|
||||||
mx.as.StateStore.SetMembership(roomID, mx.bridge.Bot.UserID, event.MembershipJoin)
|
br.AS.StateStore.SetMembership(roomID, br.Bot.UserID, event.MembershipJoin)
|
||||||
portal.Encrypted = true
|
portal.Encrypted = true
|
||||||
}
|
}
|
||||||
portal.Update(nil)
|
portal.Update(nil)
|
||||||
|
@ -217,312 +94,12 @@ func (mx *MatrixHandler) createPrivatePortalFromInvite(roomID id.RoomID, inviter
|
||||||
_, _ = intent.SendNotice(roomID, "Private chat portal created")
|
_, _ = intent.SendNotice(roomID, "Private chat portal created")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mx *MatrixHandler) HandlePuppetInvite(evt *event.Event, inviter *User, puppet *Puppet) {
|
func (br *WABridge) HandlePresence(evt *event.Event) {
|
||||||
intent := puppet.DefaultIntent()
|
user := br.GetUserByMXIDIfExists(evt.Sender)
|
||||||
|
|
||||||
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)
|
|
||||||
if user == nil || !user.IsLoggedIn() {
|
if user == nil || !user.IsLoggedIn() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
customPuppet := mx.bridge.GetPuppetByCustomMXID(user.MXID)
|
customPuppet := br.GetPuppetByCustomMXID(user.MXID)
|
||||||
// TODO move this flag to the user and/or portal data
|
// TODO move this flag to the user and/or portal data
|
||||||
if customPuppet != nil && !customPuppet.EnablePresence {
|
if customPuppet != nil && !customPuppet.EnablePresence {
|
||||||
return
|
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)
|
|
||||||
}
|
|
||||||
|
|
70
portal.go
70
portal.go
|
@ -80,8 +80,27 @@ func (br *WABridge) GetPortalByMXID(mxid id.RoomID) *Portal {
|
||||||
return portal
|
return portal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *WABridge) GetIPortalByMXID(mxid id.RoomID) bridge.Portal {
|
func (br *WABridge) GetIPortal(mxid id.RoomID) bridge.Portal {
|
||||||
return br.GetPortalByMXID(mxid)
|
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 {
|
func (br *WABridge) GetPortalByJID(key database.PortalKey) *Portal {
|
||||||
|
@ -234,10 +253,6 @@ type Portal struct {
|
||||||
relayUser *User
|
relayUser *User
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) IsEncrypted() bool {
|
|
||||||
return portal.Encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (portal *Portal) handleMessageLoopItem(msg PortalMessage) {
|
func (portal *Portal) handleMessageLoopItem(msg PortalMessage) {
|
||||||
if len(portal.MXID) == 0 {
|
if len(portal.MXID) == 0 {
|
||||||
if msg.fake == nil && msg.undecryptable == nil && (msg.evt == nil || !containsSupportedMessage(msg.evt.Message)) {
|
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) {
|
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 {
|
switch msg.evt.Type {
|
||||||
case event.EventMessage, event.EventSticker:
|
case event.EventMessage, event.EventSticker:
|
||||||
portal.HandleMatrixMessage(msg.user, msg.evt)
|
portal.HandleMatrixMessage(msg.user, msg.evt)
|
||||||
case event.EventRedaction:
|
case event.EventRedaction:
|
||||||
portal.HandleMatrixRedaction(msg.user, msg.evt)
|
portal.HandleMatrixRedaction(msg.user, msg.evt)
|
||||||
|
case event.EventReaction:
|
||||||
|
portal.HandleMatrixReaction(msg.user, msg.evt)
|
||||||
default:
|
default:
|
||||||
portal.log.Warnln("Unsupported event type %+v in portal message channel", msg.evt.Type)
|
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) {
|
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)
|
portal.log.Debugfln("Received reaction event %s from %s", evt.ID, evt.Sender)
|
||||||
err := portal.handleMatrixReaction(sender, evt)
|
err := portal.handleMatrixReaction(sender, evt)
|
||||||
if err != nil {
|
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 !sender.IsLoggedIn() {
|
||||||
if isExplicit {
|
if isExplicit {
|
||||||
portal.log.Debugfln("Ignoring read receipt by %s: user is not connected to WhatsApp", sender.JID)
|
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() {
|
if portal.IsPrivateChat() {
|
||||||
portal.log.Debugln("User left private chat portal, cleaning up and deleting...")
|
portal.log.Debugln("User left private chat portal, cleaning up and deleting...")
|
||||||
portal.Delete()
|
portal.Delete()
|
||||||
|
@ -3264,7 +3301,9 @@ func (portal *Portal) HandleMatrixLeave(sender *User) {
|
||||||
portal.CleanupIfEmpty()
|
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{
|
_, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
|
||||||
target.JID: whatsmeow.ParticipantChangeRemove,
|
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)
|
//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{
|
_, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
|
||||||
target.JID: whatsmeow.ParticipantChangeAdd,
|
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)
|
//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) {
|
switch content := evt.Content.Parsed.(type) {
|
||||||
case *event.RoomNameEventContent:
|
case *event.RoomNameEventContent:
|
||||||
if content.Name == portal.Name {
|
if content.Name == portal.Name {
|
||||||
|
|
26
puppet.go
26
puppet.go
|
@ -31,6 +31,7 @@ import (
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/appservice"
|
"maunium.net/go/mautrix/appservice"
|
||||||
|
"maunium.net/go/mautrix/bridge"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/mautrix-whatsapp/config"
|
"maunium.net/go/mautrix-whatsapp/config"
|
||||||
|
@ -104,6 +105,31 @@ func (br *WABridge) GetPuppetByCustomMXID(mxid id.UserID) *Puppet {
|
||||||
return 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 {
|
func (br *WABridge) GetAllPuppetsWithCustomMXID() []*Puppet {
|
||||||
return br.dbPuppetsToPuppets(br.DB.Puppet.GetAllWithCustomMXID())
|
return br.dbPuppetsToPuppets(br.DB.Puppet.GetAllWithCustomMXID())
|
||||||
}
|
}
|
||||||
|
|
27
user.go
27
user.go
|
@ -63,6 +63,7 @@ type User struct {
|
||||||
Admin bool
|
Admin bool
|
||||||
Whitelisted bool
|
Whitelisted bool
|
||||||
RelayWhitelisted bool
|
RelayWhitelisted bool
|
||||||
|
PermissionLevel bridge.PermissionLevel
|
||||||
|
|
||||||
mgmtCreateLock sync.Mutex
|
mgmtCreateLock sync.Mutex
|
||||||
spaceCreateLock sync.Mutex
|
spaceCreateLock sync.Mutex
|
||||||
|
@ -107,12 +108,28 @@ func (br *WABridge) GetUserByMXID(userID id.UserID) *User {
|
||||||
return br.getUserByMXID(userID, false)
|
return br.getUserByMXID(userID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *WABridge) GetIUserByMXID(userID id.UserID) bridge.User {
|
func (br *WABridge) GetIUser(userID id.UserID, create bool) bridge.User {
|
||||||
return br.getUserByMXID(userID, false)
|
u := br.getUserByMXID(userID, !create)
|
||||||
|
if u == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) IsAdmin() bool {
|
func (user *User) GetPermissionLevel() bridge.PermissionLevel {
|
||||||
return user.Admin
|
return user.PermissionLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) GetManagementRoomID() id.RoomID {
|
||||||
|
return user.ManagementRoom
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) GetMXID() id.UserID {
|
||||||
|
return user.MXID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) GetCommandState() map[string]interface{} {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *WABridge) GetUserByMXIDIfExists(userID id.UserID) *User {
|
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),
|
historySyncs: make(chan *events.HistorySync, 32),
|
||||||
lastPresence: types.PresenceUnavailable,
|
lastPresence: types.PresenceUnavailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
user.RelayWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelayWhitelisted(user.MXID)
|
user.RelayWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelayWhitelisted(user.MXID)
|
||||||
user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
|
user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)
|
||||||
user.Admin = user.bridge.Config.Bridge.Permissions.IsAdmin(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 {
|
if len(user.bridge.Config.Homeserver.StatusEndpoint) > 0 {
|
||||||
user.bridgeStateQueue = make(chan BridgeState, 10)
|
user.bridgeStateQueue = make(chan BridgeState, 10)
|
||||||
go user.bridgeStateLoop()
|
go user.bridgeStateLoop()
|
||||||
|
|
Loading…
Reference in a new issue