diff --git a/src/bot.ts b/src/bot.ts
index d2e13e14883faa3d0abe19b3bc5dd1b4fff477b4..e482314ed6dfe148da99ccfc845ea6cc0f4c034b 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -126,6 +126,14 @@ export class DiscordBot {
         return this.roomHandler;
     }
 
+    get MxEventProcessor(): MatrixEventProcessor {
+        return this.mxEventProcessor;
+    }
+
+    get Provisioner(): Provisioner {
+        return this.provisioner;
+    }
+
     public GetIntentFromDiscordMember(member: Discord.GuildMember | Discord.User, webhookID?: string): Intent {
         if (webhookID) {
             // webhookID and user IDs are the same, they are unique, so no need to prefix _webhook_
diff --git a/src/discordas.ts b/src/discordas.ts
index e395fef41d577bcc3e57c3062c768465bc9ca230..c4a33a34e6faf18412dd05f6aa7e6ebc9f89327b 100644
--- a/src/discordas.ts
+++ b/src/discordas.ts
@@ -166,11 +166,12 @@ async function run(port: number, fileConfig: DiscordBridgeConfig) {
 
     const discordbot = new DiscordBot(botUserId, config, bridge, store);
     const roomhandler = discordbot.RoomHandler;
+    const eventProcessor = discordbot.MxEventProcessor;
 
     try {
         callbacks.onAliasQueried = roomhandler.OnAliasQueried.bind(roomhandler);
         callbacks.onAliasQuery = roomhandler.OnAliasQuery.bind(roomhandler);
-        callbacks.onEvent = roomhandler.OnEvent.bind(roomhandler);
+        callbacks.onEvent = eventProcessor.OnEvent.bind(roomhandler);
         callbacks.thirdPartyLookup = async () => {
             return roomhandler.ThirdPartyLookup;
         };
diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a116d6f5f127220f2cbb347e5a6906a024b4e1ce
--- /dev/null
+++ b/src/matrixcommandhandler.ts
@@ -0,0 +1,225 @@
+import { DiscordBot } from "./bot";
+import { Log } from "./log";
+import { DiscordBridgeConfig } from "./config";
+import { Bridge, BridgeContext } from "matrix-appservice-bridge";
+import { IMatrixEvent } from "./matrixtypes";
+import { Provisioner } from "./provisioner";
+import { Util } from "./util";
+import * as Discord from "discord.js";
+const log = new Log("MatrixCommandHandler");
+
+/* tslint:disable:no-magic-numbers */
+const PROVISIONING_DEFAULT_POWER_LEVEL = 50;
+const PROVISIONING_DEFAULT_USER_POWER_LEVEL = 0;
+const ROOM_CACHE_MAXAGE_MS = 15 * 60 * 1000;
+/* tslint:enable:no-magic-numbers */
+
+export class MatrixCommandHandler {
+    private botJoinedRooms: Set<string> = new Set(); // roomids
+    private botJoinedRoomsCacheUpdatedAt = 0;
+    private provisioner: Provisioner;
+    constructor(
+        private discord: DiscordBot,
+        private bridge: Bridge,
+        private config: DiscordBridgeConfig,
+    ) {
+        this.provisioner = this.discord.Provisioner;
+    }
+
+    public async HandleInvite(event: IMatrixEvent) {
+        log.info(`Received invite for ${event.state_key} in room ${event.room_id}`);
+        if (event.state_key === this.discord.GetBotId()) {
+            log.info("Accepting invite for bridge bot");
+            await this.bridge.getIntent().joinRoom(event.room_id);
+            this.botJoinedRooms.add(event.room_id);
+        }
+    }
+
+    public async ProcessCommand(event: IMatrixEvent, context: BridgeContext) {
+        const intent = this.bridge.getIntent();
+        if (!(await this.isBotInRoom(event.room_id))) {
+            log.warn(`Bot is not in ${event.room_id}. Ignoring command`);
+            return;
+        }
+
+        if (!this.config.bridge.enableSelfServiceBridging) {
+            // We can do this here because the only commands we support are self-service bridging
+            return this.bridge.getIntent().sendMessage(event.room_id, {
+                body: "The owner of this bridge does not permit self-service bridging.",
+                msgtype: "m.notice",
+            });
+        }
+
+        // Check to make sure the user has permission to do anything in the room. We can do this here
+        // because the only commands we support are self-service commands (which therefore require some
+        // level of permissions)
+        const plEvent = await this.bridge.getIntent().getClient()
+            .getStateEvent(event.room_id, "m.room.power_levels", "");
+        let userLevel = PROVISIONING_DEFAULT_USER_POWER_LEVEL;
+        let requiredLevel = PROVISIONING_DEFAULT_POWER_LEVEL;
+        if (plEvent && plEvent.state_default) {
+            requiredLevel = plEvent.state_default;
+        }
+        if (plEvent && plEvent.users_default) {
+            userLevel = plEvent.users_default;
+        }
+        if (plEvent && plEvent.users && plEvent.users[event.sender]) {
+            userLevel = plEvent.users[event.sender];
+        }
+
+        if (userLevel < requiredLevel) {
+            return this.bridge.getIntent().sendMessage(event.room_id, {
+                body: "You do not have the required power level in this room to create a bridge to a Discord channel.",
+                msgtype: "m.notice",
+            });
+        }
+
+        const {command, args} = Util.MsgToArgs(event.content!.body as string, "!discord");
+
+        if (command === "help" && args[0] === "bridge") {
+            const link = Util.GetBotLink(this.config);
+            // tslint:disable prefer-template
+            return this.bridge.getIntent().sendMessage(event.room_id, {
+                body: "How to bridge a Discord guild:\n" +
+                "1. Invite the bot to your Discord guild using this link: " + link + "\n" +
+                "2. Invite me to the matrix room you'd like to bridge\n" +
+                "3. Open the Discord channel you'd like to bridge in a web browser\n" +
+                "4. In the matrix room, send the message `!discord bridge <guild id> <channel id>` " +
+                "(without the backticks)\n" +
+                "   Note: The Guild ID and Channel ID can be retrieved from the URL in your web browser.\n" +
+                "   The URL is formatted as https://discordapp.com/channels/GUILD_ID/CHANNEL_ID\n" +
+                "5. Enjoy your new bridge!",
+                msgtype: "m.notice",
+            });
+            // tslint:enable prefer-template
+        } else if (command === "bridge") {
+            if (context.rooms.remote) {
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "This room is already bridged to a Discord guild.",
+                    msgtype: "m.notice",
+                });
+            }
+
+            const MAXARGS = 2;
+            if (args.length > MAXARGS || args.length < 1) {
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "Invalid syntax. For more information try !discord help bridge",
+                    msgtype: "m.notice",
+                });
+            }
+
+            let guildId: string;
+            let channelId: string;
+
+            const AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY = 2;
+
+            if (args.length === AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY) { // "x y" syntax
+                guildId = args[0];
+                channelId = args[1];
+            } else if (args.length === 1 && args[0].includes("/")) { // "x/y" syntax
+                const split = args[0].split("/");
+                guildId = split[0];
+                channelId = split[1];
+            } else {
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "Invalid syntax: See `!discord help`",
+                    formatted_body: "Invalid syntax: See <code>!discord help</code>",
+                    msgtype: "m.notice",
+                });
+            }
+
+            try {
+                const discordResult = await this.discord.LookupRoom(guildId, channelId);
+                const channel = discordResult.channel as Discord.TextChannel;
+
+                log.info(`Bridging matrix room ${event.room_id} to ${guildId}/${channelId}`);
+                this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "I'm asking permission from the guild administrators to make this bridge.",
+                    msgtype: "m.notice",
+                });
+
+                await this.provisioner.AskBridgePermission(channel, event.sender);
+                await this.provisioner.BridgeMatrixRoom(channel, event.room_id);
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "I have bridged this room to your channel",
+                    msgtype: "m.notice",
+                });
+            } catch (err) {
+                if (err.message === "Timed out waiting for a response from the Discord owners"
+                    || err.message === "The bridge has been declined by the Discord guild") {
+                    return this.bridge.getIntent().sendMessage(event.room_id, {
+                        body: err.message,
+                        msgtype: "m.notice",
+                    });
+                }
+
+                log.error(`Error bridging ${event.room_id} to ${guildId}/${channelId}`);
+                log.error(err);
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "There was a problem bridging that channel - has the guild owner approved the bridge?",
+                    msgtype: "m.notice",
+                });
+            }
+        } else if (command === "unbridge") {
+            const remoteRoom = context.rooms.remote;
+
+            if (!remoteRoom) {
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "This room is not bridged.",
+                    msgtype: "m.notice",
+                });
+            }
+
+            if (!remoteRoom.data.plumbed) {
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "This room cannot be unbridged.",
+                    msgtype: "m.notice",
+                });
+            }
+
+            try {
+                await this.provisioner.UnbridgeRoom(remoteRoom);
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "This room has been unbridged",
+                    msgtype: "m.notice",
+                });
+            } catch (err) {
+                log.error("Error while unbridging room " + event.room_id);
+                log.error(err);
+                return this.bridge.getIntent().sendMessage(event.room_id, {
+                    body: "There was an error unbridging this room. " +
+                      "Please try again later or contact the bridge operator.",
+                    msgtype: "m.notice",
+                });
+            }
+        } else if (command === "help") {
+            // Unknown command or no command given to get help on, so we'll just give them the help
+            // tslint:disable prefer-template
+            return this.bridge.getIntent().sendMessage(event.room_id, {
+                body: "Available commands:\n" +
+                "!discord bridge <guild id> <channel id>   - Bridges this room to a Discord channel\n" +
+                "!discord unbridge                         - Unbridges a Discord channel from this room\n" +
+                "!discord help <command>                   - Help menu for another command. Eg: !discord help bridge\n",
+                msgtype: "m.notice",
+            });
+            // tslint:enable prefer-template
+        }
+    }
+
+    private async isBotInRoom(roomId: string): Promise<boolean> {
+        // Update the room cache, if not done already.
+        if (Date.now () - this.botJoinedRoomsCacheUpdatedAt > ROOM_CACHE_MAXAGE_MS) {
+            log.verbose("Updating room cache for bot...");
+            try {
+                log.verbose("Got new room cache for bot");
+                this.botJoinedRoomsCacheUpdatedAt = Date.now();
+                const rooms = (await this.bridge.getBot().getJoinedRooms()) as string[];
+                this.botJoinedRooms = new Set(rooms);
+            } catch (e) {
+                log.error("Failed to get room cache for bot, ", e);
+                return false;
+            }
+        }
+        return this.botJoinedRooms.has(roomId);
+    }
+}
diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts
index 2b23992eff3ce72f73f4ca0726cde6bbc7d3e921..dfab2e6488d995112de5a8008111d42eea42f609 100644
--- a/src/matrixeventprocessor.ts
+++ b/src/matrixeventprocessor.ts
@@ -21,10 +21,11 @@ import * as escapeStringRegexp from "escape-string-regexp";
 import { Util } from "./util";
 import * as path from "path";
 import * as mime from "mime";
-import { MatrixUser, Bridge } from "matrix-appservice-bridge";
+import { MatrixUser, Bridge, BridgeContext } from "matrix-appservice-bridge";
 import { Client as MatrixClient } from "matrix-js-sdk";
 import { IMatrixEvent, IMatrixEventContent, IMatrixMessage } from "./matrixtypes";
 import { MatrixMessageProcessor, IMatrixMessageProcessorParams } from "./matrixmessageprocessor";
+import { MatrixCommandHandler } from "./matrixcommandhandler";
 
 import { Log } from "./log";
 const log = new Log("MatrixEventProcessor");
@@ -34,6 +35,8 @@ const MIN_NAME_LENGTH = 2;
 const MAX_NAME_LENGTH = 32;
 const DISCORD_AVATAR_WIDTH = 128;
 const DISCORD_AVATAR_HEIGHT = 128;
+const ROOM_NAME_PARTS = 2;
+const AGE_LIMIT = 900000; // 15 * 60 * 1000
 
 export class MatrixEventProcessorOpts {
     constructor(
@@ -55,12 +58,101 @@ export class MatrixEventProcessor {
     private bridge: Bridge;
     private discord: DiscordBot;
     private matrixMsgProcessor: MatrixMessageProcessor;
+    private mxCommandHandler: MatrixCommandHandler;
 
     constructor(opts: MatrixEventProcessorOpts) {
         this.config = opts.config;
         this.bridge = opts.bridge;
         this.discord = opts.discord;
         this.matrixMsgProcessor = new MatrixMessageProcessor(this.discord);
+        this.mxCommandHandler = new MatrixCommandHandler(this.discord, this.bridge, this.config);
+    }
+
+    public async OnEvent(request, context: BridgeContext): Promise<void> {
+        const event = request.getData() as IMatrixEvent;
+        if (event.unsigned.age > AGE_LIMIT) {
+            log.warn(`Skipping event due to age ${event.unsigned.age} > ${AGE_LIMIT}`);
+            return;
+        }
+        if (
+            event.type === "m.room.member" &&
+            event.content!.membership === "invite" &&
+            event.state_key === this.discord.GetBotId()
+        ) {
+            await this.mxCommandHandler.HandleInvite(event);
+            return;
+        } else if (event.type === "m.room.member" && this.bridge.getBot().isRemoteUser(event.state_key)) {
+            if (["leave", "ban"].includes(event.content!.membership!) && event.sender !== event.state_key) {
+                // Kick/Ban handling
+                let prevMembership = "";
+                if (event.content!.membership === "leave") {
+                    const intent = this.bridge.getIntent();
+                    prevMembership = (await intent.getEvent(event.room_id, event.replaces_state)).content.membership;
+                }
+                await this.discord.HandleMatrixKickBan(
+                    event.room_id,
+                    event.state_key,
+                    event.sender,
+                    event.content!.membership as "leave"|"ban",
+                    prevMembership,
+                    event.content!.reason,
+                );
+            }
+            return;
+        } else if (["m.room.member", "m.room.name", "m.room.topic"].includes(event.type)) {
+            await this.discord.ProcessMatrixStateEvent(event);
+            return;
+        } else if (event.type === "m.room.redaction" && context.rooms.remote) {
+            await this.discord.ProcessMatrixRedact(event);
+            return;
+        } else if (event.type === "m.room.message" || event.type === "m.sticker") {
+            log.verbose(`Got ${event.type} event`);
+            const isBotCommand = event.type === "m.room.message" &&
+                event.content!.body &&
+                event.content!.body!.startsWith("!discord");
+            if (isBotCommand) {
+                await this.mxCommandHandler.ProcessCommand(event, context);
+                return;
+            } else if (context.rooms.remote) {
+                const srvChanPair = context.rooms.remote.roomId.substr("_discord".length).split("_", ROOM_NAME_PARTS);
+                try {
+                    await this.discord.ProcessMatrixMsgEvent(event, srvChanPair[0], srvChanPair[1]);
+                    return;
+                } catch (err) {
+                    log.warn("There was an error sending a matrix event", err);
+                    return;
+                }
+            }
+        } else if (event.type === "m.room.encryption" && context.rooms.remote) {
+            try {
+                await this.HandleEncryptionWarning(event.room_id);
+                return;
+            } catch (err) {
+                throw new Error(`Failed to handle encrypted room, ${err}`);
+            }
+        } else {
+            log.verbose("Got non m.room.message event");
+        }
+        log.verbose("Event not processed by bridge");
+    }
+
+    public async HandleEncryptionWarning(roomId: string): Promise<void> {
+        const intent = this.bridge.getIntent();
+        log.info(`User has turned on encryption in ${roomId}, so leaving.`);
+        /* N.B 'status' is not specced but https://github.com/matrix-org/matrix-doc/pull/828
+         has been open for over a year with no resolution. */
+        const sendPromise = intent.sendMessage(roomId, {
+            body: "You have turned on encryption in this room, so the service will not bridge any new messages.",
+            msgtype: "m.notice",
+            status: "critical",
+        });
+        const channel = await this.discord.GetChannelFromRoomId(roomId);
+        await (channel as Discord.TextChannel).send(
+          "Someone on Matrix has turned on encryption in this room, so the service will not bridge any new messages",
+        );
+        await sendPromise;
+        await intent.leave(roomId);
+        await this.bridge.getRoomStore().removeEntriesByMatrixRoomId(roomId);
     }
 
     public StateEventToMessage(event: IMatrixEvent, channel: Discord.TextChannel): string | undefined {
diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts
index 109a5f4b2f7a9b1d6b2796c4dbb99abb2dd7600b..3a38e7a6bdda11648ce0d65c18bda8186bec10f2 100644
--- a/src/matrixroomhandler.ts
+++ b/src/matrixroomhandler.ts
@@ -22,7 +22,6 @@ import {
     thirdPartyProtocolResult,
     thirdPartyUserResult,
     thirdPartyLocationResult,
-    BridgeContext,
     ProvisionedRoom,
     Intent,
 } from "matrix-appservice-bridge";
@@ -41,7 +40,6 @@ const ICON_URL = "https://matrix.org/_matrix/media/r0/download/matrix.org/mlxoES
 /* tslint:disable:no-magic-numbers */
 const HTTP_UNSUPPORTED = 501;
 const ROOM_NAME_PARTS = 2;
-const AGE_LIMIT = 900000; // 15 * 60 * 1000
 const PROVISIONING_DEFAULT_POWER_LEVEL = 50;
 const PROVISIONING_DEFAULT_USER_POWER_LEVEL = 0;
 const USERSYNC_STATE_DELAY_MS = 5000;
@@ -139,271 +137,6 @@ export class MatrixRoomHandler {
         await Promise.all(promiseList);
     }
 
-    public async OnEvent(request, context: BridgeContext): Promise<void> {
-        const event = request.getData() as IMatrixEvent;
-        if (event.unsigned.age > AGE_LIMIT) {
-            log.warn(`Skipping event due to age ${event.unsigned.age} > ${AGE_LIMIT}`);
-            return;
-        }
-        if (event.type === "m.room.member" && event.content!.membership === "invite") {
-            await this.HandleInvite(event);
-            return;
-        } else if (event.type === "m.room.member" && this.bridge.getBot().isRemoteUser(event.state_key)) {
-            if (["leave", "ban"].includes(event.content!.membership!) && event.sender !== event.state_key) {
-                // Kick/Ban handling
-                let prevMembership = "";
-                if (event.content!.membership === "leave") {
-                    const intent = this.bridge.getIntent();
-                    prevMembership = (await intent.getEvent(event.room_id, event.replaces_state)).content.membership;
-                }
-                await this.discord.HandleMatrixKickBan(
-                    event.room_id,
-                    event.state_key,
-                    event.sender,
-                    event.content!.membership as "leave"|"ban",
-                    prevMembership,
-                    event.content!.reason,
-                );
-            }
-            return;
-        } else if (["m.room.member", "m.room.name", "m.room.topic"].includes(event.type)) {
-            await this.discord.ProcessMatrixStateEvent(event);
-            return;
-        } else if (event.type === "m.room.redaction" && context.rooms.remote) {
-            await this.discord.ProcessMatrixRedact(event);
-            return;
-        } else if (event.type === "m.room.message" || event.type === "m.sticker") {
-            log.verbose(`Got ${event.type} event`);
-            const isBotCommand = event.type === "m.room.message" &&
-                event.content!.body &&
-                event.content!.body!.startsWith("!discord");
-            if (isBotCommand) {
-                await this.ProcessCommand(event, context);
-                return;
-            } else if (context.rooms.remote) {
-                const srvChanPair = context.rooms.remote.roomId.substr("_discord".length).split("_", ROOM_NAME_PARTS);
-                try {
-                    await this.discord.ProcessMatrixMsgEvent(event, srvChanPair[0], srvChanPair[1]);
-                    return;
-                } catch (err) {
-                    log.warn("There was an error sending a matrix event", err);
-                    return;
-                }
-            }
-        } else if (event.type === "m.room.encryption" && context.rooms.remote) {
-            try {
-                await this.HandleEncryptionWarning(event.room_id);
-                return;
-            } catch (err) {
-                throw new Error(`Failed to handle encrypted room, ${err}`);
-            }
-        } else {
-            log.verbose("Got non m.room.message event");
-        }
-        log.verbose("Event not processed by bridge");
-    }
-
-    public async HandleEncryptionWarning(roomId: string): Promise<void> {
-        const intent = this.bridge.getIntent();
-        log.info(`User has turned on encryption in ${roomId}, so leaving.`);
-        /* N.B 'status' is not specced but https://github.com/matrix-org/matrix-doc/pull/828
-         has been open for over a year with no resolution. */
-        const sendPromise = intent.sendMessage(roomId, {
-            body: "You have turned on encryption in this room, so the service will not bridge any new messages.",
-            msgtype: "m.notice",
-            status: "critical",
-        });
-        const channel = await this.discord.GetChannelFromRoomId(roomId);
-        await (channel as Discord.TextChannel).send(
-          "Someone on Matrix has turned on encryption in this room, so the service will not bridge any new messages",
-        );
-        await sendPromise;
-        await intent.leave(roomId);
-        await this.roomStore.removeEntriesByMatrixRoomId(roomId);
-    }
-
-    public async HandleInvite(event: IMatrixEvent) {
-        log.info(`Received invite for ${event.state_key} in room ${event.room_id}`);
-        if (event.state_key === this.botUserId) {
-            log.info("Accepting invite for bridge bot");
-            await this.joinRoom(this.bridge.getIntent(), event.room_id);
-            this.botJoinedRooms.add(event.room_id);
-        } else {
-            await this.discord.ProcessMatrixStateEvent(event);
-        }
-    }
-
-    public async ProcessCommand(event: IMatrixEvent, context: BridgeContext) {
-        const intent = this.bridge.getIntent();
-        if (!(await this.isBotInRoom(event.room_id))) {
-            log.warn(`Bot is not in ${event.room_id}. Ignoring command`);
-            return;
-        }
-
-        if (!this.config.bridge.enableSelfServiceBridging) {
-            // We can do this here because the only commands we support are self-service bridging
-            return this.bridge.getIntent().sendMessage(event.room_id, {
-                body: "The owner of this bridge does not permit self-service bridging.",
-                msgtype: "m.notice",
-            });
-        }
-
-        // Check to make sure the user has permission to do anything in the room. We can do this here
-        // because the only commands we support are self-service commands (which therefore require some
-        // level of permissions)
-        const plEvent = await this.bridge.getIntent().getClient()
-            .getStateEvent(event.room_id, "m.room.power_levels", "");
-        let userLevel = PROVISIONING_DEFAULT_USER_POWER_LEVEL;
-        let requiredLevel = PROVISIONING_DEFAULT_POWER_LEVEL;
-        if (plEvent && plEvent.state_default) {
-            requiredLevel = plEvent.state_default;
-        }
-        if (plEvent && plEvent.users_default) {
-            userLevel = plEvent.users_default;
-        }
-        if (plEvent && plEvent.users && plEvent.users[event.sender]) {
-            userLevel = plEvent.users[event.sender];
-        }
-
-        if (userLevel < requiredLevel) {
-            return this.bridge.getIntent().sendMessage(event.room_id, {
-                body: "You do not have the required power level in this room to create a bridge to a Discord channel.",
-                msgtype: "m.notice",
-            });
-        }
-
-        const {command, args} = Util.MsgToArgs(event.content!.body as string, "!discord");
-
-        if (command === "help" && args[0] === "bridge") {
-            const link = Util.GetBotLink(this.config);
-            // tslint:disable prefer-template
-            return this.bridge.getIntent().sendMessage(event.room_id, {
-                body: "How to bridge a Discord guild:\n" +
-                "1. Invite the bot to your Discord guild using this link: " + link + "\n" +
-                "2. Invite me to the matrix room you'd like to bridge\n" +
-                "3. Open the Discord channel you'd like to bridge in a web browser\n" +
-                "4. In the matrix room, send the message `!discord bridge <guild id> <channel id>` " +
-                "(without the backticks)\n" +
-                "   Note: The Guild ID and Channel ID can be retrieved from the URL in your web browser.\n" +
-                "   The URL is formatted as https://discordapp.com/channels/GUILD_ID/CHANNEL_ID\n" +
-                "5. Enjoy your new bridge!",
-                msgtype: "m.notice",
-            });
-            // tslint:enable prefer-template
-        } else if (command === "bridge") {
-            if (context.rooms.remote) {
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "This room is already bridged to a Discord guild.",
-                    msgtype: "m.notice",
-                });
-            }
-
-            const MAXARGS = 2;
-            if (args.length > MAXARGS || args.length < 1) {
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "Invalid syntax. For more information try !discord help bridge",
-                    msgtype: "m.notice",
-                });
-            }
-
-            let guildId: string;
-            let channelId: string;
-
-            const AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY = 2;
-
-            if (args.length === AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY) { // "x y" syntax
-                guildId = args[0];
-                channelId = args[1];
-            } else if (args.length === 1 && args[0].includes("/")) { // "x/y" syntax
-                const split = args[0].split("/");
-                guildId = split[0];
-                channelId = split[1];
-            } else {
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "Invalid syntax: See `!discord help`",
-                    formatted_body: "Invalid syntax: See <code>!discord help</code>",
-                    msgtype: "m.notice",
-                });
-            }
-
-            try {
-                const discordResult = await this.discord.LookupRoom(guildId, channelId);
-                const channel = discordResult.channel as Discord.TextChannel;
-
-                log.info(`Bridging matrix room ${event.room_id} to ${guildId}/${channelId}`);
-                this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "I'm asking permission from the guild administrators to make this bridge.",
-                    msgtype: "m.notice",
-                });
-
-                await this.provisioner.AskBridgePermission(channel, event.sender);
-                await this.provisioner.BridgeMatrixRoom(channel, event.room_id);
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "I have bridged this room to your channel",
-                    msgtype: "m.notice",
-                });
-            } catch (err) {
-                if (err.message === "Timed out waiting for a response from the Discord owners"
-                    || err.message === "The bridge has been declined by the Discord guild") {
-                    return this.bridge.getIntent().sendMessage(event.room_id, {
-                        body: err.message,
-                        msgtype: "m.notice",
-                    });
-                }
-
-                log.error(`Error bridging ${event.room_id} to ${guildId}/${channelId}`);
-                log.error(err);
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "There was a problem bridging that channel - has the guild owner approved the bridge?",
-                    msgtype: "m.notice",
-                });
-            }
-        } else if (command === "unbridge") {
-            const remoteRoom = context.rooms.remote;
-
-            if (!remoteRoom) {
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "This room is not bridged.",
-                    msgtype: "m.notice",
-                });
-            }
-
-            if (!remoteRoom.data.plumbed) {
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "This room cannot be unbridged.",
-                    msgtype: "m.notice",
-                });
-            }
-
-            try {
-                await this.provisioner.UnbridgeRoom(remoteRoom);
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "This room has been unbridged",
-                    msgtype: "m.notice",
-                });
-            } catch (err) {
-                log.error("Error while unbridging room " + event.room_id);
-                log.error(err);
-                return this.bridge.getIntent().sendMessage(event.room_id, {
-                    body: "There was an error unbridging this room. " +
-                      "Please try again later or contact the bridge operator.",
-                    msgtype: "m.notice",
-                });
-            }
-        } else if (command === "help") {
-            // Unknown command or no command given to get help on, so we'll just give them the help
-            // tslint:disable prefer-template
-            return this.bridge.getIntent().sendMessage(event.room_id, {
-                body: "Available commands:\n" +
-                "!discord bridge <guild id> <channel id>   - Bridges this room to a Discord channel\n" +
-                "!discord unbridge                         - Unbridges a Discord channel from this room\n" +
-                "!discord help <command>                   - Help menu for another command. Eg: !discord help bridge\n",
-                msgtype: "m.notice",
-            });
-            // tslint:enable prefer-template
-        }
-    }
-
     public async OnAliasQuery(alias: string, aliasLocalpart: string): Promise<ProvisionedRoom> {
         log.info("Got request for #", aliasLocalpart);
         const srvChanPair = aliasLocalpart.substr("_discord_".length).split("_", ROOM_NAME_PARTS);
@@ -663,21 +396,4 @@ export class MatrixRoomHandler {
             creationOpts,
         } as ProvisionedRoom;
     }
-
-    private async isBotInRoom(roomId: string): Promise<boolean> {
-        // Update the room cache, if not done already.
-        if (Date.now () - this.botJoinedRoomsCacheUpdatedAt > ROOM_CACHE_MAXAGE_MS) {
-            log.verbose("Updating room cache for bot...");
-            try {
-                log.verbose("Got new room cache for bot");
-                this.botJoinedRoomsCacheUpdatedAt = Date.now();
-                const rooms = (await this.bridge.getBot().getJoinedRooms()) as string[];
-                this.botJoinedRooms = new Set(rooms);
-            } catch (e) {
-                log.error("Failed to get room cache for bot, ", e);
-                return false;
-            }
-        }
-        return this.botJoinedRooms.has(roomId);
-    }
 }