ソースを参照

Add bridge/unbridge/delete-portal commands

Fixes #34
Tulir Asokan 2 年 前
コミット
3c52e76e15
2 ファイル変更164 行追加12 行削除
  1. 150 8
      commands.go
  2. 14 4
      portal.go

+ 150 - 8
commands.go

@@ -32,6 +32,7 @@ import (
 
 	"maunium.net/go/mautrix"
 	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/bridge/bridgeconfig"
 	"maunium.net/go/mautrix/bridge/commands"
 	"maunium.net/go/mautrix/event"
 	"maunium.net/go/mautrix/id"
@@ -58,6 +59,9 @@ func (br *DiscordBridge) RegisterCommands() {
 		cmdPing,
 		cmdReconnect,
 		cmdDisconnect,
+		cmdBridge,
+		cmdUnbridge,
+		cmdDeletePortal,
 		cmdSetRelay,
 		cmdUnsetRelay,
 		cmdGuilds,
@@ -406,14 +410,16 @@ func fnSetRelay(ce *WrappedCommandEvent) {
 			ce.Reply("Portal with room ID %s not found", ce.Args[0])
 			return
 		}
-		levels, err := portal.MainIntent().PowerLevels(ce.RoomID)
-		if err != nil {
-			ce.ZLog.Warn().Err(err).Msg("Failed to check room power levels")
-			ce.Reply("Failed to get room power levels to see if you're allowed to use that command")
-			return
-		} else if levels.GetUserLevel(ce.User.GetMXID()) < levels.GetEventLevel(roomModerator) {
-			ce.Reply("You don't have admin rights in that room")
-			return
+		if ce.User.PermissionLevel < bridgeconfig.PermissionLevelAdmin {
+			levels, err := portal.MainIntent().PowerLevels(ce.RoomID)
+			if err != nil {
+				ce.ZLog.Warn().Err(err).Msg("Failed to check room power levels")
+				ce.Reply("Failed to get room power levels to see if you're allowed to use that command")
+				return
+			} else if levels.GetUserLevel(ce.User.GetMXID()) < levels.GetEventLevel(roomModerator) {
+				ce.Reply("You don't have admin rights in that room")
+				return
+			}
 		}
 		ce.Args = ce.Args[1:]
 	} else if portal == nil {
@@ -640,6 +646,142 @@ func fnGuildBridgingMode(ce *WrappedCommandEvent) {
 	ce.Reply("Set guild bridging mode to %s", mode.Description())
 }
 
+var cmdBridge = &commands.FullHandler{
+	Func: wrapCommand(fnBridge),
+	Name: "bridge",
+	Help: commands.HelpMeta{
+		Section:     HelpSectionPortalManagement,
+		Description: "Bridge this room to a specific Discord channel",
+		Args:        "[--replace[=delete]] <_channel ID_>",
+	},
+	RequiresEventLevel: roomModerator,
+}
+
+func isNumber(str string) bool {
+	for _, chr := range str {
+		if chr < '0' || chr > '9' {
+			return false
+		}
+	}
+	return true
+}
+
+func fnBridge(ce *WrappedCommandEvent) {
+	if ce.Portal != nil {
+		ce.Reply("This is already a portal room. Unbridge with `$cmdprefix unbridge` first if you want to link it to a different channel.")
+		return
+	}
+	var channelID string
+	var unbridgeOld, deleteOld bool
+	fail := true
+	for _, arg := range ce.Args {
+		arg = strings.ToLower(arg)
+		if arg == "--replace" {
+			unbridgeOld = true
+		} else if arg == "--replace=delete" {
+			unbridgeOld = true
+			deleteOld = true
+		} else if channelID == "" && isNumber(arg) {
+			channelID = arg
+			fail = false
+		} else {
+			fail = true
+			break
+		}
+	}
+	if fail {
+		ce.Reply("**Usage**: `$cmdprefix bridge [--replace[=delete]] <channel ID>`")
+		return
+	}
+	portal := ce.User.GetExistingPortalByID(channelID)
+	if portal == nil {
+		ce.Reply("Channel not found")
+		return
+	}
+	portal.roomCreateLock.Lock()
+	defer portal.roomCreateLock.Lock()
+	if portal.MXID != "" {
+		hasUnbridgePermission := ce.User.PermissionLevel >= bridgeconfig.PermissionLevelAdmin
+		if !hasUnbridgePermission {
+			levels, err := portal.MainIntent().PowerLevels(portal.MXID)
+			if errors.Is(err, mautrix.MNotFound) {
+				ce.ZLog.Debug().Err(err).Msg("Got M_NOT_FOUND trying to get power levels to check if user can unbridge it, assuming the room is gone")
+				hasUnbridgePermission = true
+			} else if err != nil {
+				ce.ZLog.Warn().Err(err).Msg("Failed to check room power levels")
+				ce.Reply("Failed to get power levels in old room to see if you're allowed to unbridge it")
+				return
+			} else {
+				hasUnbridgePermission = levels.GetUserLevel(ce.User.GetMXID()) >= levels.GetEventLevel(roomModerator)
+			}
+		}
+		if !unbridgeOld || !hasUnbridgePermission {
+			extraHelp := "Rerun the command with `--replace` or `--replace=delete` to unbridge the old room."
+			if !hasUnbridgePermission {
+				extraHelp = "Additionally, you do not have the permissions to unbridge the old room."
+			}
+			ce.Reply("That channel is already bridged to [%s](https://matrix.to/#/%s). %s", portal.Name, portal.MXID, extraHelp)
+			return
+		}
+		ce.ZLog.Debug().
+			Str("old_room_id", portal.MXID.String()).
+			Bool("delete", deleteOld).
+			Msg("Unbridging old room")
+		portal.removeFromSpace()
+		portal.RemoveMXID()
+		portal.cleanup(deleteOld)
+		ce.ZLog.Info().
+			Str("old_room_id", portal.MXID.String()).
+			Bool("delete", deleteOld).
+			Msg("Unbridged old room to make space for new bridge")
+	}
+	if portal.Guild != nil && portal.Guild.BridgingMode < database.GuildBridgeIfPortalExists {
+		ce.ZLog.Debug().Str("guild_id", portal.Guild.ID).Msg("Bumping bridging mode of portal guild to if-portal-exists")
+		portal.Guild.BridgingMode = database.GuildBridgeIfPortalExists
+		portal.Guild.Update()
+	}
+	ce.ZLog.Debug().Str("channel_id", portal.Key.ChannelID).Msg("Bridging room")
+	portal.MXID = ce.RoomID
+	portal.updateRoomName()
+	portal.updateRoomAvatar()
+	portal.updateRoomTopic()
+	portal.updateSpace()
+	portal.UpdateBridgeInfo()
+	portal.Update()
+	ce.Reply("Room successfully bridged")
+	ce.ZLog.Info().Str("channel_id", portal.Key.ChannelID).Msg("Manual bridging complete")
+}
+
+var cmdUnbridge = &commands.FullHandler{
+	Func: wrapCommand(fnUnbridge),
+	Name: "unbridge",
+	Help: commands.HelpMeta{
+		Section:     HelpSectionPortalManagement,
+		Description: "Unbridge this room from the linked Discord channel",
+	},
+	RequiresPortal:     true,
+	RequiresEventLevel: roomModerator,
+}
+
+var cmdDeletePortal = &commands.FullHandler{
+	Func: wrapCommand(fnUnbridge),
+	Name: "delete-portal",
+	Help: commands.HelpMeta{
+		Section:     HelpSectionPortalManagement,
+		Description: "Unbridge this room and kick all Matrix users",
+	},
+	RequiresPortal:     true,
+	RequiresEventLevel: roomModerator,
+}
+
+func fnUnbridge(ce *WrappedCommandEvent) {
+	ce.Portal.roomCreateLock.Lock()
+	defer ce.Portal.roomCreateLock.Lock()
+	ce.Portal.removeFromSpace()
+	ce.Portal.RemoveMXID()
+	ce.Portal.cleanup(ce.Command == "delete-portal")
+}
+
 var cmdDeleteAllPortals = &commands.FullHandler{
 	Func: wrapCommand(fnDeleteAllPortals),
 	Name: "delete-all-portals",

+ 14 - 4
portal.go

@@ -160,7 +160,9 @@ func (br *DiscordBridge) GetExistingPortalByID(key database.PortalKey) *Portal {
 	defer br.portalsLock.Unlock()
 	portal, ok := br.portalsByID[key]
 	if !ok {
-		portal, ok = br.portalsByID[database.NewPortalKey(key.ChannelID, "")]
+		if key.Receiver != "" {
+			portal, ok = br.portalsByID[database.NewPortalKey(key.ChannelID, "")]
+		}
 		if !ok {
 			return br.loadPortal(br.DB.Portal.GetByID(key), nil, -1)
 		}
@@ -1732,6 +1734,11 @@ func (portal *Portal) UpdateNameDirect(name string) bool {
 	portal.log.Debugfln("Updating name %q -> %q", portal.Name, name)
 	portal.Name = name
 	portal.NameSet = false
+	portal.updateRoomName()
+	return true
+}
+
+func (portal *Portal) updateRoomName() {
 	if portal.MXID != "" {
 		_, err := portal.MainIntent().SetRoomName(portal.MXID, portal.Name)
 		if err != nil {
@@ -1740,7 +1747,6 @@ func (portal *Portal) UpdateNameDirect(name string) bool {
 			portal.NameSet = true
 		}
 	}
-	return true
 }
 
 func (portal *Portal) UpdateAvatarFromPuppet(puppet *Puppet) bool {
@@ -1779,7 +1785,7 @@ func (portal *Portal) UpdateGroupDMAvatar(iconID string) bool {
 }
 
 func (portal *Portal) updateRoomAvatar() {
-	if portal.MXID == "" {
+	if portal.MXID == "" || portal.AvatarURL.IsEmpty() {
 		return
 	}
 	_, err := portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL)
@@ -1797,6 +1803,11 @@ func (portal *Portal) UpdateTopic(topic string) bool {
 	portal.log.Debugfln("Updating topic %q -> %q", portal.Topic, topic)
 	portal.Topic = topic
 	portal.TopicSet = false
+	portal.updateRoomTopic()
+	return true
+}
+
+func (portal *Portal) updateRoomTopic() {
 	if portal.MXID != "" {
 		_, err := portal.MainIntent().SetRoomTopic(portal.MXID, portal.Topic)
 		if err != nil {
@@ -1805,7 +1816,6 @@ func (portal *Portal) UpdateTopic(topic string) bool {
 			portal.TopicSet = true
 		}
 	}
-	return true
 }
 
 func (portal *Portal) removeFromSpace() {