Break a bunch of stuff

This commit is contained in:
Tulir Asokan
2022-05-28 23:03:24 +03:00
parent 575f684a54
commit 91dbc83b5d
26 changed files with 1824 additions and 1002 deletions

567
user.go
View File

@@ -4,11 +4,15 @@ import (
"errors"
"fmt"
"net/http"
"os"
"runtime"
"strings"
"sync"
"time"
log "maunium.net/go/maulogger/v2"
"github.com/bwmarrin/discordgo"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/appservice"
@@ -35,12 +39,43 @@ type User struct {
PermissionLevel bridgeconfig.PermissionLevel
guilds map[string]*database.Guild
guildsLock sync.Mutex
spaceCreateLock sync.Mutex
spaceMembershipChecked bool
Session *discordgo.Session
}
var discordLog log.Logger
func init() {
discordgo.Logger = func(msgL, caller int, format string, a ...interface{}) {
pc, file, line, _ := runtime.Caller(caller + 1)
files := strings.Split(file, "/")
file = files[len(files)-1]
name := runtime.FuncForPC(pc).Name()
fns := strings.Split(name, ".")
name = fns[len(fns)-1]
msg := fmt.Sprintf(format, a...)
var level log.Level
switch msgL {
case discordgo.LogError:
level = log.LevelError
case discordgo.LogWarning:
level = log.LevelWarn
case discordgo.LogInformational:
level = log.LevelInfo
case discordgo.LogDebug:
level = log.LevelDebug
}
discordLog.Logfln(level, "%s:%d:%s() %s", file, line, name, msg)
}
}
func (user *User) GetPermissionLevel() bridgeconfig.PermissionLevel {
return user.PermissionLevel
}
@@ -66,10 +101,10 @@ func (user *User) GetIDoublePuppet() bridge.DoublePuppet {
}
func (user *User) GetIGhost() bridge.Ghost {
if user.ID == "" {
if user.DiscordID == "" {
return nil
}
p := user.bridge.GetPuppetByID(user.ID)
p := user.bridge.GetPuppetByID(user.DiscordID)
if p == nil {
return nil
}
@@ -78,14 +113,6 @@ func (user *User) GetIGhost() bridge.Ghost {
var _ bridge.User = (*User)(nil)
// this assume you are holding the guilds lock!!!
func (user *User) loadGuilds() {
user.guilds = map[string]*database.Guild{}
for _, guild := range user.bridge.DB.Guild.GetAll(user.ID) {
user.guilds[guild.GuildID] = guild
}
}
func (br *DiscordBridge) loadUser(dbUser *database.User, mxid *id.UserID) *User {
// If we weren't passed in a user we attempt to create one if we were given
// a matrix id.
@@ -103,8 +130,8 @@ func (br *DiscordBridge) loadUser(dbUser *database.User, mxid *id.UserID) *User
// We assume the usersLock was acquired by our caller.
br.usersByMXID[user.MXID] = user
if user.ID != "" {
br.usersByID[user.ID] = user
if user.DiscordID != "" {
br.usersByID[user.DiscordID] = user
}
if user.ManagementRoom != "" {
@@ -114,17 +141,10 @@ func (br *DiscordBridge) loadUser(dbUser *database.User, mxid *id.UserID) *User
br.managementRoomsLock.Unlock()
}
// Load our guilds state from the database and turn it into a map
user.guildsLock.Lock()
user.loadGuilds()
user.guildsLock.Unlock()
return user
}
func (br *DiscordBridge) GetUserByMXID(userID id.UserID) *User {
// TODO: check if puppet
br.usersLock.Lock()
defer br.usersLock.Unlock()
@@ -153,7 +173,6 @@ func (br *DiscordBridge) NewUser(dbUser *database.User) *User {
User: dbUser,
bridge: br,
log: br.Log.Sub("User").Sub(string(dbUser.MXID)),
guilds: map[string]*database.Guild{},
}
user.PermissionLevel = br.Config.Bridge.Permissions.Get(user.MXID)
@@ -161,11 +180,11 @@ func (br *DiscordBridge) NewUser(dbUser *database.User) *User {
return user
}
func (br *DiscordBridge) getAllUsers() []*User {
func (br *DiscordBridge) getAllUsersWithToken() []*User {
br.usersLock.Lock()
defer br.usersLock.Unlock()
dbUsers := br.DB.User.GetAll()
dbUsers := br.DB.User.GetAllWithToken()
users := make([]*User, len(dbUsers))
for idx, dbUser := range dbUsers {
@@ -182,7 +201,7 @@ func (br *DiscordBridge) getAllUsers() []*User {
func (br *DiscordBridge) startUsers() {
br.Log.Debugln("Starting users")
for _, u := range br.getAllUsers() {
for _, u := range br.getAllUsersWithToken() {
go func(user *User) {
err := user.Connect()
if err != nil {
@@ -209,10 +228,6 @@ func (user *User) SetManagementRoom(roomID id.RoomID) {
existing, ok := user.bridge.managementRooms[roomID]
if ok {
// If there's a user already assigned to this management room, clear it
// out.
// I think this is due a name change or something? I dunno, leaving it
// for now.
existing.ManagementRoom = ""
existing.Update()
}
@@ -222,6 +237,52 @@ func (user *User) SetManagementRoom(roomID id.RoomID) {
user.Update()
}
func (user *User) GetSpaceRoom() id.RoomID {
if len(user.SpaceRoom) == 0 {
user.spaceCreateLock.Lock()
defer user.spaceCreateLock.Unlock()
if len(user.SpaceRoom) > 0 {
return user.SpaceRoom
}
resp, err := user.bridge.Bot.CreateRoom(&mautrix.ReqCreateRoom{
Visibility: "private",
Name: "Discord",
Topic: "Your Discord bridged chats",
InitialState: []*event.Event{{
Type: event.StateRoomAvatar,
Content: event.Content{
Parsed: &event.RoomAvatarEventContent{
URL: user.bridge.Config.AppService.Bot.ParsedAvatar,
},
},
}},
CreationContent: map[string]interface{}{
"type": event.RoomTypeSpace,
},
PowerLevelOverride: &event.PowerLevelsEventContent{
Users: map[id.UserID]int{
user.bridge.Bot.UserID: 9001,
user.MXID: 50,
},
},
})
if err != nil {
user.log.Errorln("Failed to auto-create space room:", err)
} else {
user.SpaceRoom = resp.RoomID
user.Update()
user.ensureInvited(user.bridge.Bot, user.SpaceRoom, false)
}
} else if !user.spaceMembershipChecked && !user.bridge.StateStore.IsInRoom(user.SpaceRoom, user.MXID) {
user.ensureInvited(user.bridge.Bot, user.SpaceRoom, false)
}
user.spaceMembershipChecked = true
return user.SpaceRoom
}
func (user *User) tryAutomaticDoublePuppeting() {
user.Lock()
defer user.Unlock()
@@ -232,7 +293,7 @@ func (user *User) tryAutomaticDoublePuppeting() {
user.log.Debugln("Checking if double puppeting needs to be enabled")
puppet := user.bridge.GetPuppetByID(user.ID)
puppet := user.bridge.GetPuppetByID(user.DiscordID)
if puppet.CustomMXID != "" {
user.log.Debugln("User already has double-puppeting enabled")
@@ -270,7 +331,7 @@ func (user *User) syncChatDoublePuppetDetails(portal *Portal, justCreated bool)
}
func (user *User) Login(token string) error {
user.Token = token
user.DiscordToken = token
user.Update()
return user.Connect()
}
@@ -279,7 +340,7 @@ func (user *User) IsLoggedIn() bool {
user.Lock()
defer user.Unlock()
return user.Token != ""
return user.DiscordToken != ""
}
func (user *User) Logout() error {
@@ -290,7 +351,7 @@ func (user *User) Logout() error {
return ErrNotLoggedIn
}
puppet := user.bridge.GetPuppetByID(user.ID)
puppet := user.bridge.GetPuppetByID(user.DiscordID)
if puppet.CustomMXID != "" {
err := puppet.SwitchCustomMXID("", "")
if err != nil {
@@ -304,7 +365,7 @@ func (user *User) Logout() error {
user.Session = nil
user.Token = ""
user.DiscordToken = ""
user.Update()
return nil
@@ -321,16 +382,20 @@ func (user *User) Connect() error {
user.Lock()
defer user.Unlock()
if user.Token == "" {
if user.DiscordToken == "" {
return ErrNotLoggedIn
}
user.log.Debugln("connecting to discord")
user.log.Debugln("Connecting to discord")
session, err := discordgo.New(user.Token)
session, err := discordgo.New(user.DiscordToken)
if err != nil {
return err
}
// TODO move to config
if os.Getenv("DISCORD_DEBUG") == "1" {
session.LogLevel = discordgo.LogDebug
}
user.Session = session
@@ -382,263 +447,178 @@ func (user *User) bridgeMessage(guildID string) bool {
return true
}
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
if guild, found := user.guilds[guildID]; found {
if guild.Bridge {
return true
}
guild := user.bridge.GetGuildByID(guildID, false)
if guild.MXID != "" {
return true
}
user.log.Debugfln("ignoring message for non-bridged guild %s-%s", user.ID, guildID)
user.log.Debugfln("Cgnoring message for non-bridged guild %s", guildID)
return false
}
func (user *User) readyHandler(s *discordgo.Session, r *discordgo.Ready) {
user.log.Debugln("discord connection ready")
func (user *User) readyHandler(_ *discordgo.Session, r *discordgo.Ready) {
user.log.Debugln("Discord connection ready")
// Update our user fields
user.ID = r.User.ID
// Update our guild map to match watch discord thinks we're in. This is the
// only time we can get the full guild map as discordgo doesn't make it
// available to us later. Also, discord might not give us the full guild
// information here, so we use this to remove guilds the user left and only
// add guilds whose full information we have. The are told about the
// "unavailable" guilds later via the GuildCreate handler.
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
// build a list of the current guilds we're in so we can prune the old ones
current := []string{}
user.log.Debugln("database guild count", len(user.guilds))
user.log.Debugln("discord guild count", len(r.Guilds))
for _, guild := range r.Guilds {
current = append(current, guild.ID)
// If we already know about this guild, make sure we reset it's bridge
// status.
if val, found := user.guilds[guild.ID]; found {
bridge := val.Bridge
user.guilds[guild.ID].Bridge = bridge
// Update the name if the guild is available
if !guild.Unavailable {
user.guilds[guild.ID].GuildName = guild.Name
}
val.Upsert()
} else {
g := user.bridge.DB.Guild.New()
g.DiscordID = user.ID
g.GuildID = guild.ID
user.guilds[guild.ID] = g
if !guild.Unavailable {
g.GuildName = guild.Name
}
g.Upsert()
}
if user.DiscordID != r.User.ID {
user.DiscordID = r.User.ID
user.Update()
}
// Sync the guilds to the database.
user.bridge.DB.Guild.Prune(user.ID, current)
// Finally reload from the database since it purged servers we're not in
// anymore.
user.loadGuilds()
user.log.Debugln("updated database guild count", len(user.guilds))
user.Update()
updateTS := time.Now()
guildsInSpace := make(map[string]bool)
for _, guild := range user.GetGuilds() {
guildsInSpace[guild.GuildID] = guild.InSpace
}
for _, guild := range r.Guilds {
user.handleGuild(guild, updateTS, guildsInSpace[guild.ID])
}
user.PruneGuildList(updateTS)
const maxCreate = 5
for i, ch := range r.PrivateChannels {
portal := user.GetPortalByMeta(ch)
if i < maxCreate && portal.MXID == "" {
err := portal.CreateMatrixRoom(user, ch)
if err != nil {
user.log.Errorfln("Failed to create portal for private channel %s in initial sync: %v", ch.ID, err)
}
} else {
portal.UpdateInfo(user, ch)
}
}
}
func (user *User) connectedHandler(s *discordgo.Session, c *discordgo.Connect) {
user.log.Debugln("connected to discord")
func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSpace bool) {
guild := user.bridge.GetGuildByID(meta.ID, true)
guild.UpdateInfo(user, meta)
if len(meta.Channels) > 0 {
for _, ch := range meta.Channels {
portal := user.GetPortalByMeta(ch)
if guild.AutoBridgeChannels && portal.MXID == "" {
err := portal.CreateMatrixRoom(user, ch)
if err != nil {
user.log.Errorfln("Failed to create portal for guild channel %s/%s in initial sync: %v", guild.ID, ch.ID, err)
}
} else {
portal.UpdateInfo(user, ch)
}
}
}
if len(guild.MXID) > 0 && !isInSpace {
_, err := user.bridge.Bot.SendStateEvent(user.GetSpaceRoom(), event.StateSpaceChild, guild.MXID.String(), &event.SpaceChildEventContent{
Via: []string{user.bridge.AS.HomeserverDomain},
})
if err != nil {
user.log.Errorfln("Failed to add guild space %s to user space: %v", guild.MXID, err)
} else {
isInSpace = true
}
}
user.MarkInGuild(database.UserGuild{GuildID: meta.ID, Timestamp: timestamp, InSpace: isInSpace})
}
func (user *User) connectedHandler(_ *discordgo.Session, c *discordgo.Connect) {
user.log.Debugln("Connected to discord")
user.tryAutomaticDoublePuppeting()
}
func (user *User) disconnectedHandler(s *discordgo.Session, d *discordgo.Disconnect) {
user.log.Debugln("disconnected from discord")
func (user *User) disconnectedHandler(_ *discordgo.Session, d *discordgo.Disconnect) {
user.log.Debugln("Disconnected from discord")
}
func (user *User) guildCreateHandler(s *discordgo.Session, g *discordgo.GuildCreate) {
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
// If we somehow already know about the guild, just update it's name
if guild, found := user.guilds[g.ID]; found {
guild.GuildName = g.Name
guild.Upsert()
func (user *User) guildCreateHandler(_ *discordgo.Session, g *discordgo.GuildCreate) {
user.handleGuild(g.Guild, time.Now(), false)
}
func (user *User) guildDeleteHandler(_ *discordgo.Session, g *discordgo.GuildDelete) {
user.MarkNotInGuild(g.ID)
guild := user.bridge.GetGuildByID(g.ID, false)
if guild == nil || guild.MXID == "" {
return
}
// This is a brand new guild so lets get it added.
guild := user.bridge.DB.Guild.New()
guild.DiscordID = user.ID
guild.GuildID = g.ID
guild.GuildName = g.Name
guild.Upsert()
user.guilds[g.ID] = guild
// TODO clean up?
}
func (user *User) guildDeleteHandler(s *discordgo.Session, g *discordgo.GuildDelete) {
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
func (user *User) guildUpdateHandler(_ *discordgo.Session, g *discordgo.GuildUpdate) {
user.handleGuild(g.Guild, time.Now(), user.IsInSpace(g.ID))
}
if guild, found := user.guilds[g.ID]; found {
guild.Delete()
delete(user.guilds, g.ID)
user.log.Debugln("deleted guild", g.Guild.ID)
func (user *User) channelCreateHandler(_ *discordgo.Session, c *discordgo.ChannelCreate) {
if !user.bridgeMessage(c.GuildID) {
return
}
}
func (user *User) guildUpdateHandler(s *discordgo.Session, g *discordgo.GuildUpdate) {
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
// If we somehow already know about the guild, just update it's name
if guild, found := user.guilds[g.ID]; found {
guild.GuildName = g.Name
guild.Upsert()
user.log.Debugln("updated guild", g.ID)
}
}
func (user *User) createChannel(c *discordgo.Channel) {
key := database.NewPortalKey(c.ID, user.User.ID)
portal := user.bridge.GetPortalByID(key)
portal := user.GetPortalByMeta(c.Channel)
if portal.MXID != "" {
return
}
portal.Name = c.Name
portal.Topic = c.Topic
portal.Type = c.Type
if portal.Type == discordgo.ChannelTypeDM {
portal.OtherUserID = c.Recipients[0].ID
err := portal.CreateMatrixRoom(user, c.Channel)
if err != nil {
user.log.Errorfln("Error creating Matrix room for %s on channel create event: %v", c.ID, err)
}
if c.Icon != "" {
user.log.Debugln("channel icon", c.Icon)
}
portal.Update()
portal.createMatrixRoom(user, c)
}
func (user *User) channelCreateHandler(s *discordgo.Session, c *discordgo.ChannelCreate) {
user.createChannel(c.Channel)
}
func (user *User) channelDeleteHandler(s *discordgo.Session, c *discordgo.ChannelDelete) {
func (user *User) channelDeleteHandler(_ *discordgo.Session, c *discordgo.ChannelDelete) {
user.log.Debugln("channel delete handler")
}
func (user *User) channelPinsUpdateHandler(s *discordgo.Session, c *discordgo.ChannelPinsUpdate) {
func (user *User) channelPinsUpdateHandler(_ *discordgo.Session, c *discordgo.ChannelPinsUpdate) {
user.log.Debugln("channel pins update")
}
func (user *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUpdate) {
key := database.NewPortalKey(c.ID, user.User.ID)
portal := user.bridge.GetPortalByID(key)
portal.update(user, c.Channel)
func (user *User) channelUpdateHandler(_ *discordgo.Session, c *discordgo.ChannelUpdate) {
portal := user.GetPortalByMeta(c.Channel)
portal.UpdateInfo(user, c.Channel)
}
func (user *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
if !user.bridgeMessage(m.GuildID) {
func (user *User) pushPortalMessage(msg interface{}, typeName, channelID, guildID string) {
fmt.Printf("%+v\n", msg)
if !user.bridgeMessage(guildID) {
return
}
key := database.NewPortalKey(m.ChannelID, user.ID)
portal := user.bridge.GetPortalByID(key)
msg := portalDiscordMessage{
msg: m,
user: user,
portal := user.GetExistingPortalByID(channelID)
var thread *Thread
if portal == nil {
thread = user.bridge.GetThreadByID(channelID, nil)
if thread == nil || thread.Parent == nil {
user.log.Debugfln("Dropping %s in unknown channel %s/%s", typeName, guildID, channelID)
return
}
portal = thread.Parent
}
portal.discordMessages <- msg
portal.discordMessages <- portalDiscordMessage{
msg: msg,
user: user,
thread: thread,
}
}
func (user *User) messageDeleteHandler(s *discordgo.Session, m *discordgo.MessageDelete) {
if !user.bridgeMessage(m.GuildID) {
return
}
key := database.NewPortalKey(m.ChannelID, user.ID)
portal := user.bridge.GetPortalByID(key)
msg := portalDiscordMessage{
msg: m,
user: user,
}
portal.discordMessages <- msg
func (user *User) messageCreateHandler(_ *discordgo.Session, m *discordgo.MessageCreate) {
user.pushPortalMessage(m, "message create", m.ChannelID, m.GuildID)
}
func (user *User) messageUpdateHandler(s *discordgo.Session, m *discordgo.MessageUpdate) {
if !user.bridgeMessage(m.GuildID) {
return
}
key := database.NewPortalKey(m.ChannelID, user.ID)
portal := user.bridge.GetPortalByID(key)
msg := portalDiscordMessage{
msg: m,
user: user,
}
portal.discordMessages <- msg
func (user *User) messageDeleteHandler(_ *discordgo.Session, m *discordgo.MessageDelete) {
user.pushPortalMessage(m, "message delete", m.ChannelID, m.GuildID)
}
func (user *User) reactionAddHandler(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
if !user.bridgeMessage(m.MessageReaction.GuildID) {
return
}
key := database.NewPortalKey(m.ChannelID, user.User.ID)
portal := user.bridge.GetPortalByID(key)
msg := portalDiscordMessage{
msg: m,
user: user,
}
portal.discordMessages <- msg
func (user *User) messageUpdateHandler(_ *discordgo.Session, m *discordgo.MessageUpdate) {
user.pushPortalMessage(m, "message update", m.ChannelID, m.GuildID)
}
func (user *User) reactionRemoveHandler(s *discordgo.Session, m *discordgo.MessageReactionRemove) {
if !user.bridgeMessage(m.MessageReaction.GuildID) {
return
}
func (user *User) reactionAddHandler(_ *discordgo.Session, m *discordgo.MessageReactionAdd) {
user.pushPortalMessage(m, "reaction add", m.ChannelID, m.GuildID)
}
key := database.NewPortalKey(m.ChannelID, user.User.ID)
portal := user.bridge.GetPortalByID(key)
msg := portalDiscordMessage{
msg: m,
user: user,
}
portal.discordMessages <- msg
func (user *User) reactionRemoveHandler(_ *discordgo.Session, m *discordgo.MessageReactionRemove) {
user.pushPortalMessage(m, "reaction remove", m.ChannelID, m.GuildID)
}
func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool {
if intent == nil {
intent = user.bridge.Bot
}
ret := false
inviteContent := event.Content{
@@ -682,7 +662,7 @@ func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID,
func (user *User) getDirectChats() map[id.UserID][]id.RoomID {
chats := map[id.UserID][]id.RoomID{}
privateChats := user.bridge.DB.Portal.FindPrivateChatsOf(user.ID)
privateChats := user.bridge.DB.Portal.FindPrivateChatsOf(user.DiscordID)
for _, portal := range privateChats {
if portal.MXID != "" {
puppetMXID := user.bridge.FormatPuppetMXID(portal.Key.Receiver)
@@ -755,28 +735,21 @@ func (user *User) updateDirectChats(chats map[id.UserID][]id.RoomID) {
}
func (user *User) bridgeGuild(guildID string, everything bool) error {
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
guild, found := user.guilds[guildID]
if !found {
return fmt.Errorf("guildID not found")
guild := user.bridge.GetGuildByID(guildID, false)
if guild == nil {
return errors.New("guild not found")
}
// Update the guild
guild.Bridge = true
guild.Upsert()
// If this is a full bridge, create portals for all the channels
if everything {
channels, err := user.Session.GuildChannels(guildID)
if err != nil {
return err
}
for _, channel := range channels {
if channelIsBridgeable(channel) {
user.createChannel(channel)
meta, _ := user.Session.State.Guild(guildID)
err := guild.CreateMatrixRoom(user, meta)
if err != nil {
return err
}
for _, ch := range meta.Channels {
portal := user.GetPortalByMeta(ch)
if (everything && channelIsBridgeable(ch)) || ch.Type == discordgo.ChannelTypeGuildCategory {
err = portal.CreateMatrixRoom(user, ch)
if err != nil {
user.log.Warnfln("Error creating room for guild channel %s: %v", ch.ID, err)
}
}
}
@@ -785,41 +758,41 @@ func (user *User) bridgeGuild(guildID string, everything bool) error {
}
func (user *User) unbridgeGuild(guildID string) error {
user.guildsLock.Lock()
defer user.guildsLock.Unlock()
guild, exists := user.guilds[guildID]
if !exists {
return fmt.Errorf("guildID not found")
}
if !guild.Bridge {
return fmt.Errorf("guild not bridged")
}
// First update the guild so we don't have any other go routines recreating
// channels we're about to destroy.
guild.Bridge = false
guild.Upsert()
// Now run through the channels in the guild and remove any portals we
// have for them.
channels, err := user.Session.GuildChannels(guildID)
if err != nil {
return err
}
for _, channel := range channels {
if channelIsBridgeable(channel) {
key := database.PortalKey{
ChannelID: channel.ID,
Receiver: user.ID,
}
portal := user.bridge.GetPortalByID(key)
portal.leave(user)
}
}
//user.guildsLock.Lock()
//defer user.guildsLock.Unlock()
//
//guild, exists := user.guilds[guildID]
//if !exists {
// return fmt.Errorf("guildID not found")
//}
//
//if !guild.Bridge {
// return fmt.Errorf("guild not bridged")
//}
//
//// First update the guild so we don't have any other go routines recreating
//// channels we're about to destroy.
//guild.Bridge = false
//guild.Upsert()
//
//// Now run through the channels in the guild and remove any portals we
//// have for them.
//channels, err := user.Session.GuildChannels(guildID)
//if err != nil {
// return err
//}
//
//for _, channel := range channels {
// if channelIsBridgeable(channel) {
// key := database.PortalKey{
// ChannelID: channel.ID,
// Receiver: user.DiscordID,
// }
//
// portal := user.bridge.GetPortalByID(key)
// portal.leave(user)
// }
//}
return nil
}