From 11931714a64593baf9d114f6fcea2ef46a48b2f8 Mon Sep 17 00:00:00 2001 From: Ian Chechin Date: Wed, 13 May 2026 14:53:25 +0800 Subject: [PATCH] bridgev2: add bridge_remote_leave config to keep users in Matrix after they leave on the remote side Symmetric counterpart of the existing bridge_matrix_leave option. When the bridge sees a membership change from the remote network with Membership == leave and the operator has set bridge_remote_leave: false, syncUser drops the leave instead of applying it to the corresponding Matrix room. The user keeps access to the room and its existing history; new traffic between them and the remote group no longer bridges because the remote relationship is gone. The example config and the upgrade list grow a new entry, defaulting to true so existing deployments keep the current behavior on a silent upgrade. The "set the power level to 0 and raise the send-message level" follow-up suggested in #450 is intentionally out of scope here; it needs a broader power-level handling discussion. With this change in place, an operator can keep the user in the room and the bot can still moderate further behavior on top. Closes #450. --- bridgev2/bridgeconfig/config.go | 1 + bridgev2/bridgeconfig/upgrade.go | 1 + bridgev2/matrix/mxmain/example-config.yaml | 6 ++++++ bridgev2/portal.go | 10 ++++++++++ 4 files changed, 18 insertions(+) diff --git a/bridgev2/bridgeconfig/config.go b/bridgev2/bridgeconfig/config.go index acfbad79..f1e8e46e 100644 --- a/bridgev2/bridgeconfig/config.go +++ b/bridgev2/bridgeconfig/config.go @@ -76,6 +76,7 @@ type BridgeConfig struct { UnknownErrorAutoReconnect time.Duration `yaml:"unknown_error_auto_reconnect"` UnknownErrorMaxAutoReconnects int `yaml:"unknown_error_max_auto_reconnects"` BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"` + BridgeRemoteLeave bool `yaml:"bridge_remote_leave"` BridgeNotices bool `yaml:"bridge_notices"` TagOnlyOnCreate bool `yaml:"tag_only_on_create"` OnlyBridgeTags []event.RoomTag `yaml:"only_bridge_tags"` diff --git a/bridgev2/bridgeconfig/upgrade.go b/bridgev2/bridgeconfig/upgrade.go index 54c54d57..7e3ed7e9 100644 --- a/bridgev2/bridgeconfig/upgrade.go +++ b/bridgev2/bridgeconfig/upgrade.go @@ -35,6 +35,7 @@ func doUpgrade(helper up.Helper) { helper.Copy(up.Str|up.Int|up.Null, "bridge", "unknown_error_auto_reconnect") helper.Copy(up.Int, "bridge", "unknown_error_max_auto_reconnects") helper.Copy(up.Bool, "bridge", "bridge_matrix_leave") + helper.Copy(up.Bool, "bridge", "bridge_remote_leave") helper.Copy(up.Bool, "bridge", "bridge_notices") helper.Copy(up.Bool, "bridge", "tag_only_on_create") helper.Copy(up.List, "bridge", "only_bridge_tags") diff --git a/bridgev2/matrix/mxmain/example-config.yaml b/bridgev2/matrix/mxmain/example-config.yaml index 28903a65..eef85057 100644 --- a/bridgev2/matrix/mxmain/example-config.yaml +++ b/bridgev2/matrix/mxmain/example-config.yaml @@ -35,6 +35,12 @@ bridge: # Should leaving Matrix rooms be bridged as leaving groups on the remote network? bridge_matrix_leave: false + # Should leaving a group on the remote network kick the user out of the + # corresponding Matrix room? When `true`, a remote leave is propagated to + # Matrix the same as any other membership change. When `false`, the leave + # is dropped so the user keeps access to history and stays in the Matrix + # room. + bridge_remote_leave: true # Should `m.notice` messages be bridged? bridge_notices: false # Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority. diff --git a/bridgev2/portal.go b/bridgev2/portal.go index 5620992e..63af1576 100644 --- a/bridgev2/portal.go +++ b/bridgev2/portal.go @@ -4618,6 +4618,16 @@ func (portal *Portal) syncParticipants( if member.Membership == "" { member.Membership = event.MembershipJoin } + // Symmetric with bridge_matrix_leave: when bridge_remote_leave is + // disabled, dropping the leave from the remote network keeps the + // user in the Matrix room with their existing history. + if !portal.Bridge.Config.BridgeRemoteLeave && member.Membership == event.MembershipLeave { + log.Debug(). + Stringer("user_id", extraUserID). + Msg("Dropping remote leave: bridge_remote_leave is disabled") + delete(currentMembers, extraUserID) + return false + } if member.PowerLevel != nil { powerChanged = currentPower.EnsureUserLevelAs(portal.Bridge.Bot.GetMXID(), extraUserID, *member.PowerLevel) || powerChanged }