From bb9a0f6528a0c5d2e81b3860da4ff58da33a9c52 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 28 Oct 2021 13:57:15 +0300 Subject: [PATCH] Make relay mode more like the Signal bridge --- commands.go | 72 ++++++++------- config/bridge.go | 10 +- database/portal.go | 22 +++-- .../upgrades/2021-10-28-portal-relay-user.go | 12 +++ database/upgrades/upgrades.go | 2 +- example-config.yaml | 13 +-- main.go | 39 +++----- matrix.go | 6 -- portal.go | 92 +++++++------------ user.go | 4 - 10 files changed, 129 insertions(+), 143 deletions(-) create mode 100644 database/upgrades/2021-10-28-portal-relay-user.go diff --git a/commands.go b/commands.go index 8007eab..e3b618f 100644 --- a/commands.go +++ b/commands.go @@ -93,17 +93,11 @@ func (handler *CommandHandler) Handle(roomID id.RoomID, user *User, message stri Args: args[1:], } handler.log.Debugfln("%s sent '%s' in %s", user.MXID, message, roomID) - if roomID == handler.bridge.Config.Bridge.Relaybot.ManagementRoom { - handler.CommandRelaybot(ce) - } else { - handler.CommandMux(ce) - } + handler.CommandMux(ce) } func (handler *CommandHandler) CommandMux(ce *CommandEvent) { switch ce.Command { - case "relaybot": - handler.CommandRelaybot(ce) case "login": handler.CommandLogin(ce) case "logout-matrix": @@ -134,7 +128,7 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) { handler.CommandLogout(ce) case "toggle": handler.CommandToggle(ce) - case "login-matrix", "sync", "list", "open", "pm", "invite-link", "join", "create": + case "set-relay", "unset-relay", "login-matrix", "sync", "list", "open", "pm", "invite-link", "join", "create": if !ce.User.HasSession() { ce.Reply("You are not logged in. Use the `login` command to log into WhatsApp.") return @@ -144,6 +138,10 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) { } switch ce.Command { + case "set-relay": + handler.CommandSetRelay(ce) + case "unset-relay": + handler.CommandUnsetRelay(ce) case "login-matrix": handler.CommandLoginMatrix(ce) case "list": @@ -175,22 +173,35 @@ func (handler *CommandHandler) CommandDiscardMegolmSession(ce *CommandEvent) { } } -func (handler *CommandHandler) CommandRelaybot(ce *CommandEvent) { - if handler.bridge.Relaybot == nil { - ce.Reply("The relaybot is disabled") - } else if !ce.User.Admin { - ce.Reply("Only admins can manage the relaybot") +const cmdSetRelayHelp = `set-relay - Relay messages in this room through your WhatsApp account.` + +func (handler *CommandHandler) CommandSetRelay(ce *CommandEvent) { + if !handler.bridge.Config.Bridge.Relay.Enabled { + ce.Reply("Relay mode is not enabled on this instance of the bridge") + } else if ce.Portal == nil { + ce.Reply("This is not a portal room") + } else if handler.bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin { + ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge") } else { - if ce.Command == "relaybot" { - if len(ce.Args) == 0 { - ce.Reply("**Usage:** `relaybot `") - return - } - ce.Command = strings.ToLower(ce.Args[0]) - ce.Args = ce.Args[1:] - } - ce.User = handler.bridge.Relaybot - handler.CommandMux(ce) + ce.Portal.RelayUserID = ce.User.MXID + ce.Portal.Update() + ce.Reply("Messages from non-logged-in users in this room will now be bridged through your WhatsApp account") + } +} + +const cmdUnsetRelayHelp = `set-relay - Stop relaying messages in this room.` + +func (handler *CommandHandler) CommandUnsetRelay(ce *CommandEvent) { + if !handler.bridge.Config.Bridge.Relay.Enabled { + ce.Reply("Relay mode is not enabled on this instance of the bridge") + } else if ce.Portal == nil { + ce.Reply("This is not a portal room") + } else if handler.bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin { + ce.Reply("Only admins are allowed to enable relay mode on this instance of the bridge") + } else { + ce.Portal.RelayUserID = "" + ce.Portal.Update() + ce.Reply("Messages from non-logged-in users will no longer be bridged in this room") } } @@ -627,7 +638,7 @@ const cmdHelpHelp = `help - Prints this help` // CommandHelp handles help command func (handler *CommandHandler) CommandHelp(ce *CommandEvent) { cmdPrefix := "" - if ce.User.ManagementRoom != ce.RoomID || ce.User.IsRelaybot { + if ce.User.ManagementRoom != ce.RoomID { cmdPrefix = handler.bridge.Config.Bridge.CommandPrefix + " " } @@ -640,6 +651,8 @@ func (handler *CommandHandler) CommandHelp(ce *CommandEvent) { cmdPrefix + cmdReconnectHelp, cmdPrefix + cmdDisconnectHelp, cmdPrefix + cmdPingHelp, + cmdPrefix + cmdSetRelayHelp, + cmdPrefix + cmdUnsetRelayHelp, cmdPrefix + cmdLoginMatrixHelp, cmdPrefix + cmdLogoutMatrixHelp, cmdPrefix + cmdToggleHelp, @@ -871,14 +884,9 @@ func (handler *CommandHandler) CommandPM(ce *CommandEvent) { puppet.SyncContact(user, true) portal := user.GetPortalByJID(puppet.JID) if len(portal.MXID) > 0 { - if !user.IsRelaybot { - _, err = portal.MainIntent().Client.InviteUser(portal.MXID, &mautrix.ReqInviteUser{UserID: user.MXID}) - if httpErr, ok := err.(mautrix.HTTPError); ok && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") { - err = nil - } - } - if err != nil { - portal.log.Warnfln("Failed to invite %s to portal: %v. Creating new portal", user.MXID, err) + ok := portal.ensureUserInvited(user) + if !ok { + portal.log.Warnfln("ensureUserInvited(%s) returned false, creating new portal", user.MXID) portal.MXID = "" } else { ce.Reply("You already have a private chat portal with that user at [%s](https://matrix.to/#/%s)", puppet.Displayname, portal.MXID) diff --git a/config/bridge.go b/config/bridge.go index 7000d92..6c54f1f 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -85,7 +85,7 @@ type BridgeConfig struct { Permissions PermissionConfig `yaml:"permissions"` - Relaybot RelaybotConfig `yaml:"relaybot"` + Relay RelaybotConfig `yaml:"relay"` usernameTemplate *template.Template `yaml:"-"` displaynameTemplate *template.Template `yaml:"-"` @@ -110,6 +110,8 @@ func (bc *BridgeConfig) setDefaults() { bc.BridgeNotices = true bc.EnableStatusBroadcast = true + + bc.Relay.AdminOnly = true } type umBridgeConfig BridgeConfig @@ -271,10 +273,8 @@ func (pc PermissionConfig) GetPermissionLevel(userID id.UserID) PermissionLevel } type RelaybotConfig struct { - Enabled bool `yaml:"enabled"` - ManagementRoom id.RoomID `yaml:"management"` - InviteUsers []id.UserID `yaml:"invites"` - + Enabled bool `yaml:"enabled"` + AdminOnly bool `yaml:"admin_only"` MessageFormats map[event.MessageType]string `yaml:"message_formats"` messageTemplates *template.Template `yaml:"-"` } diff --git a/database/portal.go b/database/portal.go index 39d0764..ef82e71 100644 --- a/database/portal.go +++ b/database/portal.go @@ -121,11 +121,13 @@ type Portal struct { FirstEventID id.EventID NextBatchID id.BatchID + + RelayUserID id.UserID } func (portal *Portal) Scan(row Scannable) *Portal { - var mxid, avatarURL, firstEventID, nextBatchID sql.NullString - err := row.Scan(&portal.Key.JID, &portal.Key.Receiver, &mxid, &portal.Name, &portal.Topic, &portal.Avatar, &avatarURL, &portal.Encrypted, &firstEventID, &nextBatchID) + var mxid, avatarURL, firstEventID, nextBatchID, relayUserID sql.NullString + err := row.Scan(&portal.Key.JID, &portal.Key.Receiver, &mxid, &portal.Name, &portal.Topic, &portal.Avatar, &avatarURL, &portal.Encrypted, &firstEventID, &nextBatchID, &relayUserID) if err != nil { if err != sql.ErrNoRows { portal.log.Errorln("Database scan failed:", err) @@ -136,6 +138,7 @@ func (portal *Portal) Scan(row Scannable) *Portal { portal.AvatarURL, _ = id.ParseContentURI(avatarURL.String) portal.FirstEventID = id.EventID(firstEventID.String) portal.NextBatchID = id.BatchID(nextBatchID.String) + portal.RelayUserID = id.UserID(relayUserID.String) return portal } @@ -146,17 +149,24 @@ func (portal *Portal) mxidPtr() *id.RoomID { return nil } +func (portal *Portal) relayUserPtr() *id.UserID { + if len(portal.RelayUserID) > 0 { + return &portal.RelayUserID + } + return nil +} + func (portal *Portal) Insert() { - _, err := portal.db.Exec("INSERT INTO portal (jid, receiver, mxid, name, topic, avatar, avatar_url, encrypted, first_event_id, next_batch_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", - portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String()) + _, err := portal.db.Exec("INSERT INTO portal (jid, receiver, mxid, name, topic, avatar, avatar_url, encrypted, first_event_id, next_batch_id, relay_user_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr()) if err != nil { portal.log.Warnfln("Failed to insert %s: %v", portal.Key, err) } } func (portal *Portal) Update() { - _, err := portal.db.Exec("UPDATE portal SET mxid=$1, name=$2, topic=$3, avatar=$4, avatar_url=$5, encrypted=$6, first_event_id=$7, next_batch_id=$8 WHERE jid=$9 AND receiver=$10", - portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.Key.JID, portal.Key.Receiver) + _, err := portal.db.Exec("UPDATE portal SET mxid=$3, name=$4, topic=$5, avatar=$6, avatar_url=$7, encrypted=$8, first_event_id=$9, next_batch_id=$10, relay_user_id=$11 WHERE jid=$1 AND receiver=$2", + portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr()) if err != nil { portal.log.Warnfln("Failed to update %s: %v", portal.Key, err) } diff --git a/database/upgrades/2021-10-28-portal-relay-user.go b/database/upgrades/2021-10-28-portal-relay-user.go new file mode 100644 index 0000000..81beedc --- /dev/null +++ b/database/upgrades/2021-10-28-portal-relay-user.go @@ -0,0 +1,12 @@ +package upgrades + +import ( + "database/sql" +) + +func init() { + upgrades[28] = upgrade{"Add relay user field to portal table", func(tx *sql.Tx, ctx context) error { + _, err := tx.Exec(`ALTER TABLE portal ADD COLUMN relay_user_id TEXT`) + return err + }} +} diff --git a/database/upgrades/upgrades.go b/database/upgrades/upgrades.go index 13949c6..c0d9260 100644 --- a/database/upgrades/upgrades.go +++ b/database/upgrades/upgrades.go @@ -39,7 +39,7 @@ type upgrade struct { fn upgradeFunc } -const NumberOfUpgrades = 28 +const NumberOfUpgrades = 29 var upgrades [NumberOfUpgrades]upgrade diff --git a/example-config.yaml b/example-config.yaml index dac7dd7..ef32deb 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -204,15 +204,12 @@ bridge: "example.com": user "@admin:example.com": admin - relaybot: - # Whether or not relaybot support is enabled. + relay: + # Whether relay mode should be allowed. If allowed, `!signal set-relay` can be used to turn any + # authenticated user into a relaybot for that chat. enabled: false - # The management room for the bot. This is where all status notifications are posted and - # in this room, you can use `!wa ` instead of `!wa relaybot `. Omitting - # the command prefix completely like in user management rooms is not possible. - management: "!foo:example.com" - # List of users to invite to all created rooms that include the relaybot. - invites: [] + # Should only admins be allowed to set themselves as relay users? + admin_only: true # The formats to use when sending messages to WhatsApp via the relaybot. message_formats: m.text: "{{ .Sender.Displayname }}: {{ .Message }}" diff --git a/main.go b/main.go index 77a7558..bf5dd32 100644 --- a/main.go +++ b/main.go @@ -47,21 +47,29 @@ import ( "maunium.net/go/mautrix-whatsapp/database/upgrades" ) +// The name and repo URL of the bridge. var ( - // These are static Name = "mautrix-whatsapp" URL = "https://github.com/mautrix/whatsapp" - // This is changed when making a release - Version = "0.1.8" - // This is filled by init() - WAVersion = "" - VersionString = "" - // These are filled at build time with the -X linker flag +) + +// Information to find out exactly which commit the bridge was built from. +// These are filled at build time with the -X linker flag. +var ( Tag = "unknown" Commit = "unknown" BuildTime = "unknown" ) +var ( + // Version is the version number of the bridge. Changed manually when making a release. + Version = "0.1.8" + // WAVersion is the version number exposed to WhatsApp. Filled in init() + WAVersion = "" + // VersionString is the bridge version, plus commit information. Filled in init() using the build-time values. + VersionString = "" +) + func init() { if len(Tag) > 0 && Tag[0] == 'v' { Tag = Tag[1:] @@ -151,7 +159,6 @@ type Bridge struct { Provisioning *ProvisioningAPI Bot *appservice.IntentAPI Formatter *Formatter - Relaybot *User Crypto Crypto Metrics *MetricsHandler WAContainer *sqlstore.Container @@ -320,7 +327,6 @@ func (bridge *Bridge) Start() { bridge.Log.Debugln("Initializing provisioning API") bridge.Provisioning.Init() } - bridge.LoadRelaybot() bridge.Log.Debugln("Starting application service HTTP server") go bridge.AS.Start() bridge.Log.Debugln("Starting event processor") @@ -353,21 +359,6 @@ func (bridge *Bridge) ResendBridgeInfo() { bridge.Log.Infoln("Finished re-sending bridge info state events") } -func (bridge *Bridge) LoadRelaybot() { - if !bridge.Config.Bridge.Relaybot.Enabled { - return - } - bridge.Relaybot = bridge.GetUserByMXID("relaybot") - if bridge.Relaybot.HasSession() { - bridge.Log.Debugln("Relaybot is enabled") - } else { - bridge.Log.Debugln("Relaybot is enabled, but not logged in") - } - bridge.Relaybot.ManagementRoom = bridge.Config.Bridge.Relaybot.ManagementRoom - bridge.Relaybot.IsRelaybot = true - bridge.Relaybot.Connect() -} - func (bridge *Bridge) UpdateBotProfile() { bridge.Log.Debugln("Updating bot profile") botConfig := bridge.Config.AppService.Bot diff --git a/matrix.go b/matrix.go index 01ac380..70f485c 100644 --- a/matrix.go +++ b/matrix.go @@ -114,12 +114,6 @@ func (mx *MatrixHandler) HandleBotInvite(evt *event.Event) { return } - if evt.RoomID == mx.bridge.Config.Bridge.Relaybot.ManagementRoom { - _, _ = intent.SendNotice(evt.RoomID, "This is the relaybot management room. Send `!wa help` to get a list of commands.") - mx.log.Debugln("Joined relaybot management room", evt.RoomID, "after invite from", evt.Sender) - return - } - hasPuppets := false for mxid, _ := range members.Joined { if mxid == intent.UserID || mxid == evt.Sender { diff --git a/portal.go b/portal.go index f55603e..e418fb6 100644 --- a/portal.go +++ b/portal.go @@ -190,7 +190,7 @@ type Portal struct { messages chan PortalMessage - hasRelaybot *bool + relayUser *User } func (portal *Portal) handleMessageLoop() { @@ -519,14 +519,17 @@ func (portal *Portal) SyncParticipants(source *User, metadata *types.GroupInfo) participantMap := make(map[types.JID]bool) for _, participant := range metadata.Participants { participantMap[participant.JID] = true - user := portal.bridge.GetUserByJID(participant.JID) - portal.userMXIDAction(user, portal.ensureMXIDInvited) - puppet := portal.bridge.GetPuppetByJID(participant.JID) puppet.SyncContact(source, true) - err = puppet.IntentFor(portal).EnsureJoined(portal.MXID) - if err != nil { - portal.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.JID, portal.MXID, err) + user := portal.bridge.GetUserByJID(participant.JID) + if user != nil { + portal.ensureUserInvited(user) + } + if user == nil || !puppet.IntentFor(portal).IsCustomPuppet { + err = puppet.IntentFor(portal).EnsureJoined(portal.MXID) + if err != nil { + portal.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.JID, portal.MXID, err) + } } expectedLevel := 0 @@ -692,20 +695,6 @@ func (portal *Portal) UpdateMetadata(user *User) bool { return update } -func (portal *Portal) userMXIDAction(user *User, fn func(mxid id.UserID)) { - if user == nil { - return - } - - if user == portal.bridge.Relaybot { - for _, mxid := range portal.bridge.Config.Bridge.Relaybot.InviteUsers { - fn(mxid) - } - } else { - fn(user.MXID) - } -} - func (portal *Portal) ensureMXIDInvited(mxid id.UserID) { err := portal.MainIntent().EnsureInvited(portal.MXID, mxid) if err != nil { @@ -713,12 +702,7 @@ func (portal *Portal) ensureMXIDInvited(mxid id.UserID) { } } -func (portal *Portal) ensureUserInvited(user *User) { - if user.IsRelaybot { - portal.userMXIDAction(user, portal.ensureMXIDInvited) - return - } - +func (portal *Portal) ensureUserInvited(user *User) (ok bool) { inviteContent := event.Content{ Parsed: &event.MemberEventContent{ Membership: event.MembershipInvite, @@ -734,26 +718,28 @@ func (portal *Portal) ensureUserInvited(user *User) { var httpErr mautrix.HTTPError if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") { portal.bridge.StateStore.SetMembership(portal.MXID, user.MXID, event.MembershipJoin) + ok = true } else if err != nil { portal.log.Warnfln("Failed to invite %s: %v", user.MXID, err) + } else { + ok = true } if customPuppet != nil && customPuppet.CustomIntent() != nil { err = customPuppet.CustomIntent().EnsureJoined(portal.MXID) if err != nil { portal.log.Warnfln("Failed to auto-join portal as %s: %v", user.MXID, err) + ok = false + } else { + ok = true } } + return } func (portal *Portal) Sync(user *User) bool { portal.log.Infoln("Syncing portal for", user.MXID) - if user.IsRelaybot { - yes := true - portal.hasRelaybot = &yes - } - if len(portal.MXID) == 0 { err := portal.CreateMatrixRoom(user) if err != nil { @@ -1299,9 +1285,6 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { } var invite []id.UserID - if user.IsRelaybot { - invite = portal.bridge.Config.Bridge.Relaybot.InviteUsers - } if portal.bridge.Config.Bridge.Encryption.Default { initialState = append(initialState, &event.Event{ @@ -1354,7 +1337,7 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { //if broadcastMetadata != nil { // portal.SyncBroadcastRecipients(user, broadcastMetadata) //} - if portal.IsPrivateChat() && !user.IsRelaybot { + if portal.IsPrivateChat() { puppet := user.bridge.GetPuppetByJID(portal.Key.JID) if portal.bridge.Config.Bridge.Encryption.Default { @@ -1397,15 +1380,16 @@ func (portal *Portal) IsStatusBroadcastList() bool { } func (portal *Portal) HasRelaybot() bool { - if portal.bridge.Relaybot == nil { - return false - } else if portal.hasRelaybot == nil { - // FIXME - //val := portal.bridge.Relaybot.IsInPortal(portal.Key) - val := true - portal.hasRelaybot = &val + return portal.bridge.Config.Bridge.Relay.Enabled && len(portal.RelayUserID) > 0 +} + +func (portal *Portal) GetRelayUser() *User { + if !portal.HasRelaybot() { + return nil + } else if portal.relayUser == nil { + portal.relayUser = portal.bridge.GetUserByMXID(portal.RelayUserID) } - return *portal.hasRelaybot + return portal.relayUser } func (portal *Portal) MainIntent() *appservice.IntentAPI { @@ -1426,8 +1410,8 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, replyToID typ portal.log.Warnln("Failed to get reply target:", err) return } + _ = evt.Content.ParseRaw(evt.Type) if evt.Type == event.EventEncrypted { - _ = evt.Content.ParseRaw(evt.Type) decryptedEvt, err := portal.bridge.Crypto.Decrypt(evt) if err != nil { portal.log.Warnln("Failed to decrypt reply target:", err) @@ -1435,7 +1419,6 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, replyToID typ evt = decryptedEvt } } - _ = evt.Content.ParseRaw(evt.Type) content.SetReply(evt) } return @@ -2176,7 +2159,7 @@ func (portal *Portal) addRelaybotFormat(sender *User, content *event.MessageEven content.FormattedBody = strings.Replace(html.EscapeString(content.Body), "\n", "
", -1) content.Format = event.FormatHTML } - data, err := portal.bridge.Config.Bridge.Relaybot.FormatMessage(content, sender.MXID, member) + data, err := portal.bridge.Config.Bridge.Relay.FormatMessage(content, sender.MXID, member) if err != nil { portal.log.Errorln("Failed to apply relaybot format:", err) } @@ -2243,18 +2226,13 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waP } } relaybotFormatted := false - if sender.NeedsRelaybot(portal) { + if !sender.HasSession() { if !portal.HasRelaybot() { - if sender.HasSession() { - portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway") - } else { - portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") - return nil, sender - } - } else { - relaybotFormatted = portal.addRelaybotFormat(sender, content) - sender = portal.bridge.Relaybot + portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") + return nil, sender } + relaybotFormatted = portal.addRelaybotFormat(sender, content) + sender = portal.GetRelayUser() } if evt.Type == event.EventSticker { content.MsgType = event.MsgImage diff --git a/user.go b/user.go index 2ac6355..9ed813e 100644 --- a/user.go +++ b/user.go @@ -58,8 +58,6 @@ type User struct { Whitelisted bool RelaybotWhitelisted bool - IsRelaybot bool - mgmtCreateLock sync.Mutex connLock sync.Mutex @@ -179,8 +177,6 @@ func (bridge *Bridge) NewUser(dbUser *database.User) *User { log: bridge.Log.Sub("User").Sub(string(dbUser.MXID)), historySyncs: make(chan *events.HistorySync, 32), - - IsRelaybot: false, } user.RelaybotWhitelisted = user.bridge.Config.Bridge.Permissions.IsRelaybotWhitelisted(user.MXID) user.Whitelisted = user.bridge.Config.Bridge.Permissions.IsWhitelisted(user.MXID)