diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts
index 09d2fa48978fc694066afeb63933a7b990221235..d3c00a319eef1f6c9d90d52e75fb7f5deefee60a 100644
--- a/src/discordcommandhandler.ts
+++ b/src/discordcommandhandler.ts
@@ -1,6 +1,6 @@
 import { DiscordBot } from "./bot";
 import * as Discord from "discord.js";
-import { Util, ICommandActions, ICommandParameters } from "./util";
+import { Util, ICommandActions, ICommandParameters, ICommandPermissonCheck } from "./util";
 import { Bridge } from "matrix-appservice-bridge";
 export class DiscordCommandHandler {
     constructor(
diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts
index ea1e8e72912265a53516698450ee5595d7be39e2..66ddabb9fbf6853663b5ec354e1688fcc91e2274 100644
--- a/src/matrixcommandhandler.ts
+++ b/src/matrixcommandhandler.ts
@@ -47,25 +47,84 @@ export class MatrixCommandHandler {
         const actions: ICommandActions = {
             bridge: {
                 description: "Bridges this room to a Discord channel",
+                // tslint:disable prefer-template
+                help: "How to bridge a Discord guild:\n" +
+                    "1. Invite the bot to your Discord guild using this link: " + Util.GetBotLink(this.config) + "\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!",
+                // tslint:enable prefer-template
                 params: ["guildid", "channelId"],
                 permission: {
+                    cat: "events",
                     level: PROVISIONING_DEFAULT_POWER_LEVEL,
                     selfService: true,
+                    subcat: "m.room.power_levels",
                 },
                 run: async ({guildId, channelId}) => {
                     // TODO: parse guildId/channelId
 
+                    if (context.rooms.remote) {
+                        return "This room is already bridged to a Discord guild";
+                    }
+                    if (!guildId || !channelId) {
+                        return "Invalid syntax. For more information try `!discord help bridge`";
+                    }
+                    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 "I have bridged this room to your channel";
+                    } 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 err.message;
+                        }
+
+                        log.error(`Error bridging ${event.room_id} to ${guildId}/${channelId}`);
+                        log.error(err);
+                        return "There was a problem bridging that channel - has the guild owner approved the bridge?";
+                    }
                 },
             },
             unbridge: {
                 description: "Unbridges a Discord channel from this room",
                 params: [],
                 permission: {
+                    cat: "events",
                     level: PROVISIONING_DEFAULT_POWER_LEVEL,
                     selfService: true,
+                    subcat: "m.room.power_levels",
                 },
                 run: async () => {
-
+                    const remoteRoom = context.rooms.remote;
+                    if (!remoteRoom) {
+                        return "This room is not bridged.";
+                    }
+                    if (!remoteRoom.data.plumbed) {
+                        return "This room cannot be unbridged.";
+                    }
+                    try {
+                        await this.provisioner.UnbridgeRoom(remoteRoom);
+                        return "This room has been unbridged";
+                    } catch (err) {
+                        log.error("Error while unbridging room " + event.room_id);
+                        log.error(err);
+                        return "There was an error unbridging this room. " +
+                            "Please try again later or contact the bridge operator.";
+                    }
                 }
             },
         };
@@ -83,21 +142,18 @@ export class MatrixCommandHandler {
             if (permission.selfService && !this.config.bridge.enableSelfServiceBridging) {
                 return false;
             }
-            const plEvent = await this.bridge.getIntent().getClient()
-                .getStateEvent(event.room_id, "m.room.power_levels", "");
-            let userLevel = PROVISIONING_DEFAULT_USER_POWER_LEVEL;
-            let requiredLevel = permission.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];
-            }
+            return await Util.CheckMatrixPermission(
+                this.bridge.getIntent().getClient(),
+                event.sender,
+                event.room_id,
+                permission.level,
+                permission.cat,
+                permission.subcat,
+            );
         };
 
+
+
         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, {
@@ -106,160 +162,12 @@ export class MatrixCommandHandler {
             });
         }
 
-        // 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",
-                });
-            }
+        const reply = await Util.ParseCommand("!discord", event.content!.body!, actions, parameters, permissionCheck);
 
-            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
-        }
+        await this.bridge.getIntent().sendMessage(event.room_id, {
+            body: reply,
+            msgtype: "m.notice",
+        });
     }
 
     private async isBotInRoom(roomId: string): Promise<boolean> {
diff --git a/src/matrixmessageprocessor.ts b/src/matrixmessageprocessor.ts
index 91d7e5ad543f0b2bd4caa48c94972cc95cecd034..c455aac924af09f6cd5f94f1aecfab1df512202c 100644
--- a/src/matrixmessageprocessor.ts
+++ b/src/matrixmessageprocessor.ts
@@ -80,20 +80,14 @@ export class MatrixMessageProcessor {
         if (!this.params || !this.params.mxClient || !this.params.roomId || !this.params.userId) {
             return false;
         }
-
-        const res: IMatrixEvent = await this.params.mxClient.getStateEvent(
-            this.params.roomId, "m.room.power_levels");
-
-        // TODO: utilize default values correctly
-        // Some rooms may not have notifications.room set if the value hasn't
-        // been changed from the default. If so, use our hardcoded power level.
-        const requiredPowerLevel = res && res.notifications && res.notifications.room
-            ? res.notifications.room
-            : DEFAULT_ROOM_NOTIFY_POWER_LEVEL;
-
-        return res && res.users
-            && res.users[this.params.userId] !== undefined
-            && res.users[this.params.userId] >= requiredPowerLevel;
+        return await Util.CheckMatrixPermission(
+            this.params.mxClient,
+            this.params.userId,
+            this.params.roomId,
+            DEFAULT_ROOM_NOTIFY_POWER_LEVEL,
+            "notifications",
+            "room",
+        );
     }
 
     private async escapeDiscord(msg: string): Promise<string> {
diff --git a/src/util.ts b/src/util.ts
index 1d308bb04e26cf368627e01253e5a87a38b88f30..c5ed597bc17ded67814efee8dda3e0cfbc006fc1 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -21,6 +21,8 @@ import { Buffer } from "buffer";
 import * as mime from "mime";
 import { Permissions } from "discord.js";
 import { DiscordBridgeConfig } from "./config";
+import { Client as MatrixClient } from "matrix-js-sdk";
+import { IMatrixEvent } from "./matrixtypes";
 
 const HTTP_OK = 200;
 
@@ -43,7 +45,7 @@ export interface ICommandActions {
 
 export interface ICommandParameter {
     description?: string;
-    get(param: string)?: Promise<any>; // tslint:disable-line no-any
+    get?(param: string): Promise<any>; // tslint:disable-line no-any
 }
 
 export interface ICommandParameters {
@@ -51,7 +53,7 @@ export interface ICommandParameters {
 }
 
 export interface ICommandPermissonCheck {
-    (permission: PERMISSIONTYPES): Promise<bool>;
+    (permission: PERMISSIONTYPES): Promise<boolean>;
 }
 
 export interface IPatternMap {
@@ -240,8 +242,8 @@ export class Util {
         actions: ICommandActions,
         parameters: ICommandParameters,
         args: string[],
-        permissionCheck: ICommandPermissonCheck?,
-    ): string {
+        permissionCheck?: ICommandPermissonCheck,
+    ): Promise<string> {
         let reply = "";
         if (args[0]) {
             const actionKey = args[0];
@@ -285,10 +287,10 @@ export class Util {
     public static async ParseCommand(
         prefix: string,
         msg: string,
-        actions: ICommandAction[],
+        actions: ICommandActions,
         parameters: ICommandParameters,
-        permissionCheck: ICommandPermissonCheck?,
-    ): string {
+        permissionCheck?: ICommandPermissonCheck,
+    ): Promise<string> {
         const {command, args} = Util.MsgToArgs(msg, prefix);
 
         if (command === "help") {
@@ -309,8 +311,8 @@ export class Util {
             const params = {};
             let i = 0;
             for (const param of action.params) {
-                if (parameters[param].get) {
-                    params[param] = await parameters[param].get(args[i]);
+                if (parameters[param].get !== undefined) {
+                    params[param] = await parameters[param].get!(args[i]);
                 } else {
                     params[param] = args[i];
                 }
@@ -366,6 +368,35 @@ export class Util {
         }
         return str;
     }
+
+    public static async CheckMatrixPermission(
+        mxClient: MatrixClient,
+        userId: string,
+        roomId: string,
+        defaultLevel: number,
+        cat: string,
+        subcat?: string,
+    ) {
+        const res: IMatrixEvent = await mxClient.getStateEvent(roomId, "m.room.power_levels");
+        let requiredLevel = defaultLevel;
+        if (res && (res[cat] || !subcat)) {
+            if (subcat) {
+                if (res[cat][subcat] !== undefined) {
+                    requiredLevel = res[cat][subcat];
+                }
+            } else {
+                if (res[cat] !== undefined) {
+                    requiredLevel = res[cat];
+                }
+            }
+        }
+
+        let haveLevel = 0;
+        if (res && res.users && res.users[userId] !== undefined) {
+            haveLevel = res.users[userId];
+        }
+        return haveLevel >= requiredLevel;
+    }
 }
 
 interface IUploadResult {