diff --git a/src/bot.ts b/src/bot.ts index a77c32ed0f1714c076dffe06fd19faa7cf69198a..5242f2cce2eed15b41b7cd41e35d0ea4ce655b4b 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -36,6 +36,7 @@ import { Log } from "./log"; import * as Discord from "discord.js"; import * as mime from "mime"; import { IMatrixEvent, IMatrixMediaInfo } from "./matrixtypes"; +import { DiscordCommandHandler } from "./discordcommandhandler"; const log = new Log("DiscordBot"); @@ -74,6 +75,7 @@ export class DiscordBot { private channelSync: ChannelSyncroniser; private roomHandler: MatrixRoomHandler; private provisioner: Provisioner; + private discordCommandHandler: DiscordCommandHandler; /* Caches */ private roomIdsForGuildCache: Map<string, {roomIds: string[], ts: number}> = new Map(); @@ -99,6 +101,7 @@ export class DiscordBot { new MatrixEventProcessorOpts(config, bridge, this), ); this.channelSync = new ChannelSyncroniser(bridge, config, this, store.roomStore); + this.discordCommandHandler = new DiscordCommandHandler(this); // init vars this.sentMessages = []; this.discordMessageQueue = {}; @@ -711,7 +714,7 @@ export class DiscordBot { // check if it is a command to process by the bot itself if (msg.content.startsWith("!matrix")) { - await this.roomHandler.HandleDiscordCommand(msg); + await this.discordCommandHandler.Process(msg); return; } diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d76d544a7158210149bc5f70ad6948ccdc72c90 --- /dev/null +++ b/src/discordcommandhandler.ts @@ -0,0 +1,120 @@ +import { DiscordBot } from "./bot"; +import * as Discord from "discord.js"; +import { Uitl, ICommandActions, ICommandParameters } from "./util"; +export class DiscordCommandHandler { + constructor( + private discord: DiscordBot; + ) { } + + public async Process(msg: Discord.Message) { + if (!(msg.channel as Discord.TextChannel).guild) { + await msg.channel.send("**ERROR:** only available for guild channels"); + return; + } + + const {command, args} = Util.MsgToArgs(msg.content, "!matrix"); + + const intent = this.bridge.getIntent(); + + const actions: ICommandActions = { + ban: { + description: "Bans a user on the matrix side", + params: ["name"], + permission: "BAN_MEMBERS", + run: this.ModerationActionGenerator(msg.channel as Discord.TextChannel, "ban", "Banned"), + }, + kick: { + description: "Kicks a user on the matrix side", + params: ["name"], + permission: "KICK_MEMBERS", + run: this.ModerationActionGenerator(msg.channel as Discord.TextChannel, "kick", "Kicked"), + }, + unban: { + description: "Unbans a user on the matrix side", + params: ["name"], + permission: "BAN_MEMBERS", + run: this.ModerationActionGenerator(msg.channel as Discord.TextChannel, "unban", "Unbanned"), + }, + }; + + const parameters: ICommandParameters = { + name: { + description: "The display name or mxid of a matrix user", + get: async (name) => { + const channelMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(msg.channel); + const mxUserId = await Util.GetMxidFromName(intent, name, channelMxids); + return mxUserId; + }, + }, + }; + + 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; + } + + await msg.channel.send(replyMessage); + } + + private ModerationActionGenerator(discordChannel: Discord.TextChannel, funcKey: string, action: string) { + return async ({name}) => { + let allChannelMxids: string[] = []; + await Promise.all(discordChannel.guild.channels.map(async (chan) => { + try { + const chanMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(chan); + allChannelMxids = allChannelMxids.concat(chanMxids); + } catch (e) { + // pass, non-text-channel + } + })); + let errorMsg = ""; + await Promise.all(allChannelMxids.map(async (chanMxid) => { + const intent = this.bridge.getIntent(); + try { + await intent[funcKey](chanMxid, name); + } catch (e) { + // maybe we don't have permission to kick/ban/unban...? + errorMsg += `\nCouldn't ${funcKey} ${name} from ${chanMxid}`; + } + })); + if (errorMsg) { + throw Error(errorMsg); + } + return `${action} ${name}`; + }; + } +} diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts index a116d6f5f127220f2cbb347e5a6906a024b4e1ce..36ed7d71c1cfed3f029fb0680e4073e8363555e0 100644 --- a/src/matrixcommandhandler.ts +++ b/src/matrixcommandhandler.ts @@ -35,7 +35,7 @@ export class MatrixCommandHandler { } } - public async ProcessCommand(event: IMatrixEvent, context: BridgeContext) { + public async Process(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`); diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts index 28945add25b48c88d2916a52682cc2e429a99d63..7c7f6a516ccfd7114329f177280c2429eefb76ce 100644 --- a/src/matrixeventprocessor.ts +++ b/src/matrixeventprocessor.ts @@ -115,7 +115,7 @@ export class MatrixEventProcessor { event.content!.body && event.content!.body!.startsWith("!discord"); if (isBotCommand) { - await this.mxCommandHandler.ProcessCommand(event, context); + await this.mxCommandHandler.Process(event, context); return; } else if (context.rooms.remote) { const srvChanPair = context.rooms.remote.roomId.substr("_discord".length).split("_", ROOM_NAME_PARTS); diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts index c648f6e503884cd2395bf076b76a375959c72fd9..a91d4c76eb22b7e366a73a8e8ee093f76d85783e 100644 --- a/src/matrixroomhandler.ts +++ b/src/matrixroomhandler.ts @@ -28,7 +28,7 @@ import { import { DiscordBridgeConfig } from "./config"; import * as Discord from "discord.js"; -import { Util, ICommandActions, ICommandParameters } from "./util"; +import { Util } from "./util"; import { Provisioner } from "./provisioner"; import { Log } from "./log"; const log = new Log("MatrixRoomHandler"); @@ -218,117 +218,6 @@ export class MatrixRoomHandler { throw {err: "Unsupported", code: HTTP_UNSUPPORTED}; } - public async HandleDiscordCommand(msg: Discord.Message) { - if (!(msg.channel as Discord.TextChannel).guild) { - await msg.channel.send("**ERROR:** only available for guild channels"); - } - - const {command, args} = Util.MsgToArgs(msg.content, "!matrix"); - - const intent = this.bridge.getIntent(); - - const actions: ICommandActions = { - ban: { - description: "Bans a user on the matrix side", - params: ["name"], - permission: "BAN_MEMBERS", - run: this.DiscordModerationActionGenerator(msg.channel as Discord.TextChannel, "ban", "Banned"), - }, - kick: { - description: "Kicks a user on the matrix side", - params: ["name"], - permission: "KICK_MEMBERS", - run: this.DiscordModerationActionGenerator(msg.channel as Discord.TextChannel, "kick", "Kicked"), - }, - unban: { - description: "Unbans a user on the matrix side", - params: ["name"], - permission: "BAN_MEMBERS", - run: this.DiscordModerationActionGenerator(msg.channel as Discord.TextChannel, "unban", "Unbanned"), - }, - }; - - const parameters: ICommandParameters = { - name: { - description: "The display name or mxid of a matrix user", - get: async (name) => { - const channelMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(msg.channel); - const mxUserId = await Util.GetMxidFromName(intent, name, channelMxids); - return mxUserId; - }, - }, - }; - - 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; - } - - await msg.channel.send(replyMessage); - } - - private DiscordModerationActionGenerator(discordChannel: Discord.TextChannel, funcKey: string, action: string) { - return async ({name}) => { - let allChannelMxids: string[] = []; - await Promise.all(discordChannel.guild.channels.map(async (chan) => { - try { - const chanMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(chan); - allChannelMxids = allChannelMxids.concat(chanMxids); - } catch (e) { - // pass, non-text-channel - } - })); - let errorMsg = ""; - await Promise.all(allChannelMxids.map(async (chanMxid) => { - const intent = this.bridge.getIntent(); - try { - await intent[funcKey](chanMxid, name); - } catch (e) { - // maybe we don't have permission to kick/ban/unban...? - errorMsg += `\nCouldn't ${funcKey} ${name} from ${chanMxid}`; - } - })); - if (errorMsg) { - throw Error(errorMsg); - } - return `${action} ${name}`; - }; - } - private async joinRoom(intent: Intent, roomIdOrAlias: string, member?: Discord.GuildMember): Promise<void> { let currentSchedule = JOIN_ROOM_SCHEDULE[0]; const doJoin = async () => { diff --git a/test/test_matrixcommandhandler.ts b/test/test_matrixcommandhandler.ts index 8bef1b1462b331713d18f43a834cc69eb97587b0..78f4b13fab38a350fa2e93ab1a71cd3780a513d9 100644 --- a/test/test_matrixcommandhandler.ts +++ b/test/test_matrixcommandhandler.ts @@ -96,24 +96,24 @@ function createCH(opts: any = {}) { } describe("MatrixCommandHandler", () => { - describe("ProcessCommand", () => { + describe("Process", () => { it("should not process command if not in room", async () => { const handler: any = createCH({disableSS: true}); - const ret = await handler.ProcessCommand({ + const ret = await handler.Process({ room_id: "!666:localhost", }); expect(ret).to.be.undefined; }); it("should warn if self service is disabled", async () => { const handler: any = createCH({disableSS: true}); - await handler.ProcessCommand({ + await handler.Process({ room_id: "!123:localhost", }); expect(MESSAGESENT.body).equals("The owner of this bridge does not permit self-service bridging."); }); it("should warn if user is not powerful enough with defaults", async () => { const handler: any = createCH(); - await handler.ProcessCommand({ + await handler.Process({ room_id: "!123:localhost", }); expect(MESSAGESENT.body).equals("You do not have the required power level in this room to " + @@ -123,7 +123,7 @@ describe("MatrixCommandHandler", () => { const handler: any = createCH({powerLevels: { state_default: 67, }}); - await handler.ProcessCommand({ + await handler.Process({ room_id: "!123:localhost", }); expect(MESSAGESENT.body).equals("You do not have the required power level in this room to " + @@ -133,7 +133,7 @@ describe("MatrixCommandHandler", () => { const handler: any = createCH({powerLevels: { users_default: 60, }}); - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord help"}, room_id: "!123:localhost", }); @@ -145,7 +145,7 @@ describe("MatrixCommandHandler", () => { "@user:localhost": 100, }, }}); - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord help"}, room_id: "!123:localhost", sender: "@user:localhost", @@ -158,7 +158,7 @@ describe("MatrixCommandHandler", () => { users_default: 100, }}); const context = {rooms: {}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge 123 456"}, room_id: "!123:localhost", }, context); @@ -172,7 +172,7 @@ describe("MatrixCommandHandler", () => { }, }); const context = {rooms: {}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge 123 456"}, room_id: "!123:localhost", }, context); @@ -186,7 +186,7 @@ describe("MatrixCommandHandler", () => { }, }); const context = {rooms: {}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge 123 456"}, room_id: "!123:localhost", }, context); @@ -200,7 +200,7 @@ describe("MatrixCommandHandler", () => { }, }); const context = {rooms: { remote: true }}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge"}, room_id: "!123:localhost", }, context); @@ -213,7 +213,7 @@ describe("MatrixCommandHandler", () => { }, }); const context = {rooms: {}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge"}, room_id: "!123:localhost", }, context); @@ -224,7 +224,7 @@ describe("MatrixCommandHandler", () => { users_default: 100, }}); const context = {rooms: {}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord bridge 123/456"}, room_id: "!123:localhost", }, context); @@ -243,7 +243,7 @@ describe("MatrixCommandHandler", () => { plumbed: true, }, } }}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord unbridge"}, room_id: "!123:localhost", }, context); @@ -256,7 +256,7 @@ describe("MatrixCommandHandler", () => { }, }); const context = {rooms: { remote: undefined }}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord unbridge"}, room_id: "!123:localhost", }, context); @@ -273,7 +273,7 @@ describe("MatrixCommandHandler", () => { plumbed: false, }, }}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord unbridge"}, room_id: "!123:localhost", }, context); @@ -291,7 +291,7 @@ describe("MatrixCommandHandler", () => { plumbed: true, }, }}}; - const evt = await handler.ProcessCommand({ + const evt = await handler.Process({ content: {body: "!discord unbridge"}, room_id: "!123:localhost", }, context);