diff --git a/src/bot.ts b/src/bot.ts index 5242f2cce2eed15b41b7cd41e35d0ea4ce655b4b..30a0455e35a17b7c80ae3c021365b1b60fbe00e2 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -101,7 +101,7 @@ export class DiscordBot { new MatrixEventProcessorOpts(config, bridge, this), ); this.channelSync = new ChannelSyncroniser(bridge, config, this, store.roomStore); - this.discordCommandHandler = new DiscordCommandHandler(this); + this.discordCommandHandler = new DiscordCommandHandler(bridge, this); // init vars this.sentMessages = []; this.discordMessageQueue = {}; diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts index 1d76d544a7158210149bc5f70ad6948ccdc72c90..09d2fa48978fc694066afeb63933a7b990221235 100644 --- a/src/discordcommandhandler.ts +++ b/src/discordcommandhandler.ts @@ -1,9 +1,11 @@ import { DiscordBot } from "./bot"; import * as Discord from "discord.js"; -import { Uitl, ICommandActions, ICommandParameters } from "./util"; +import { Util, ICommandActions, ICommandParameters } from "./util"; +import { Bridge } from "matrix-appservice-bridge"; export class DiscordCommandHandler { constructor( - private discord: DiscordBot; + private bridge: Bridge, + private discord: DiscordBot, ) { } public async Process(msg: Discord.Message) { @@ -12,8 +14,6 @@ export class DiscordCommandHandler { return; } - const {command, args} = Util.MsgToArgs(msg.content, "!matrix"); - const intent = this.bridge.getIntent(); const actions: ICommandActions = { @@ -48,46 +48,12 @@ export class DiscordCommandHandler { }, }; - if (command === "help") { - let replyHelpMessage = "Available Commands:\n"; - for (const actionKey of Object.keys(actions)) { - const action = actions[actionKey]; - if (!msg.member.hasPermission(action.permission as Discord.PermissionResolvable)) { - continue; - } - replyHelpMessage += " - `!matrix " + actionKey; - for (const param of action.params) { - replyHelpMessage += ` <${param}>`; - } - replyHelpMessage += `\`: ${action.description}\n`; - } - replyHelpMessage += "\nParameters:\n"; - for (const parameterKey of Object.keys(parameters)) { - const parameter = parameters[parameterKey]; - replyHelpMessage += ` - \`<${parameterKey}>\`: ${parameter.description}\n`; - } - await msg.channel.send(replyHelpMessage); - return; - } - - if (!actions[command]) { - await msg.channel.send("**Error:** unknown command. Try `!matrix help` to see all commands"); - return; - } - - if (!msg.member.hasPermission(actions[command].permission as Discord.PermissionResolvable)) { - await msg.channel.send("**ERROR:** insufficiant permissions to use this matrix command"); - return; - } - - let replyMessage = ""; - try { - replyMessage = await Util.ParseCommand(actions[command], parameters, args); - } catch (e) { - replyMessage = "**ERROR:** " + e.message; + const permissionCheck: ICommandPermissonCheck = async (permission) => { + return msg.member.hasPermission(permission as Discord.PermissionResolvable); } - await msg.channel.send(replyMessage); + const reply = await Util.ParseCommand("!matrix", msg.content, actions, parameters, permissionCheck); + await msg.channel.send(reply); } private ModerationActionGenerator(discordChannel: Discord.TextChannel, funcKey: string, action: string) { diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts index 36ed7d71c1cfed3f029fb0680e4073e8363555e0..ea1e8e72912265a53516698450ee5595d7be39e2 100644 --- a/src/matrixcommandhandler.ts +++ b/src/matrixcommandhandler.ts @@ -4,7 +4,7 @@ import { DiscordBridgeConfig } from "./config"; import { Bridge, BridgeContext } from "matrix-appservice-bridge"; import { IMatrixEvent } from "./matrixtypes"; import { Provisioner } from "./provisioner"; -import { Util } from "./util"; +import { Util, ICommandActions, ICommandParameters, ICommandPermissonCheck } from "./util"; import * as Discord from "discord.js"; const log = new Log("MatrixCommandHandler"); @@ -42,6 +42,62 @@ export class MatrixCommandHandler { return; } + const {command, args} = Util.MsgToArgs(event.content!.body as string, "!discord"); + + const actions: ICommandActions = { + bridge: { + description: "Bridges this room to a Discord channel", + params: ["guildid", "channelId"], + permission: { + level: PROVISIONING_DEFAULT_POWER_LEVEL, + selfService: true, + }, + run: async ({guildId, channelId}) => { + // TODO: parse guildId/channelId + + }, + }, + unbridge: { + description: "Unbridges a Discord channel from this room", + params: [], + permission: { + level: PROVISIONING_DEFAULT_POWER_LEVEL, + selfService: true, + }, + run: async () => { + + } + }, + }; + + const parameters: ICommandParameters = { + guildId: { + description: "The ID of a guild/server on discord", + }, + channelId: { + description: "The ID of a channel on discord", + }, + }; + + const permissionCheck: ICommandPermissonCheck = async (permission) => { + 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]; + } + }; + 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, { diff --git a/src/matrixmessageprocessor.ts b/src/matrixmessageprocessor.ts index 124978d7689fb22620c6ac60d9bf76e8d7217b70..91d7e5ad543f0b2bd4caa48c94972cc95cecd034 100644 --- a/src/matrixmessageprocessor.ts +++ b/src/matrixmessageprocessor.ts @@ -84,6 +84,7 @@ export class MatrixMessageProcessor { 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 diff --git a/src/util.ts b/src/util.ts index aca4d651a2eb9780ff8d1a5666b389799edb10db..1d308bb04e26cf368627e01253e5a87a38b88f30 100644 --- a/src/util.ts +++ b/src/util.ts @@ -27,11 +27,14 @@ const HTTP_OK = 200; import { Log } from "./log"; const log = new Log("Util"); +type PERMISSIONTYPES = any; // tslint:disable-line no-any + export interface ICommandAction { params: string[]; description?: string; - permission?: string; + permission?: PERMISSIONTYPES; run(params: any): Promise<any>; // tslint:disable-line no-any + help?: string; } export interface ICommandActions { @@ -40,13 +43,17 @@ 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 { [index: string]: ICommandParameter; } +export interface ICommandPermissonCheck { + (permission: PERMISSIONTYPES): Promise<bool>; +} + export interface IPatternMap { [index: string]: string; } @@ -228,19 +235,93 @@ export class Util { return Object.keys(matrixUsers)[0]; } - public static async ParseCommand(action: ICommandAction, parameters: ICommandParameters, args: string[]) { + public static async ParseHelpMessage( + prefix: string, + actions: ICommandActions, + parameters: ICommandParameters, + args: string[], + permissionCheck: ICommandPermissonCheck?, + ): string { + let reply = ""; + if (args[0]) { + const actionKey = args[0]; + const action = actions[actionKey]; + if (!actions[actionKey]) { + return `**ERROR:** unknown command! Try \`${prefix} help\` to see all commands`; + } + if (action.permission !== undefined && permissionCheck && !(await permissionCheck(action.permission))) { + return `**ERROR:** permission denied! Try \`${prefix} help\` to see all available commands`; + } + reply += `\`${prefix} ${actionKey}`; + for (const param of action.params) { + reply += ` <${param}>`; + } + reply += `\`: ${action.description}\n`; + if (action.help) { + reply += action.help; + } + return reply; + } + reply += "Available Commands:\n"; + for (const actionKey of Object.keys(actions)) { + const action = actions[actionKey]; + if (action.permission !== undefined && permissionCheck && !(await permissionCheck(action.permission))) { + continue; + } + reply += ` - \`${prefix} ${actionKey}`; + for (const param of action.params) { + reply += ` <${param}>`; + } + reply += `\`: ${action.description}\n`; + } + reply += "\nParameters:\n"; + for (const parameterKey of Object.keys(parameters)) { + const parameter = parameters[parameterKey]; + reply += ` - \`<${parameterKey}>\`: ${parameter.description}\n`; + } + return reply; + } + + public static async ParseCommand( + prefix: string, + msg: string, + actions: ICommandAction[], + parameters: ICommandParameters, + permissionCheck: ICommandPermissonCheck?, + ): string { + const {command, args} = Util.MsgToArgs(msg, prefix); + + if (command === "help") { + return await Util.ParseHelpMessage(prefix, actions, parameters, args, permissionCheck); + } + + if (!actions[command]) { + return `**ERROR:** unknown command. Try \`${prefix} help\` to see all commands`; + } + const action = actions[command]; + if (action.permission !== undefined && permissionCheck && !permissionCheck(action.permission)) { + return `**ERROR:** insufficiant permissions to use this command`; + } if (action.params.length === 1) { args[0] = args.join(" "); } - const params = {}; - let i = 0; - for (const param of action.params) { - params[param] = await parameters[param].get(args[i]); - i++; - } + try { + const params = {}; + let i = 0; + for (const param of action.params) { + if (parameters[param].get) { + params[param] = await parameters[param].get(args[i]); + } else { + params[param] = args[i]; + } + i++; + } - const retStr = await action.run(params); - return retStr; + const retStr = await action.run(params); + return retStr; + } catch (e) { + return `**ERROR:** ${e.message}`; + } } public static MsgToArgs(msg: string, prefix: string) {