diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts index d3c00a319eef1f6c9d90d52e75fb7f5deefee60a..efcb53fc637140d1663042d2b88b44aa3d04bede 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, ICommandPermissonCheck } from "./util"; +import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from "./util"; import { Bridge } from "matrix-appservice-bridge"; export class DiscordCommandHandler { constructor( @@ -48,9 +48,9 @@ export class DiscordCommandHandler { }, }; - const permissionCheck: ICommandPermissonCheck = async (permission) => { + const permissionCheck: CommandPermissonCheck = async (permission) => { return msg.member.hasPermission(permission as Discord.PermissionResolvable); - } + }; const reply = await Util.ParseCommand("!matrix", msg.content, actions, parameters, permissionCheck); await msg.channel.send(reply); diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts index 9402a9ea3098b60d047eceefb111cc66aedd09b0..b2bbcf040a959d60547099fbed09493835974541 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, ICommandActions, ICommandParameters, ICommandPermissonCheck } from "./util"; +import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from "./util"; import * as Discord from "discord.js"; const log = new Log("MatrixCommandHandler"); @@ -123,7 +123,7 @@ export class MatrixCommandHandler { return "There was an error unbridging this room. " + "Please try again later or contact the bridge operator."; } - } + }, }, }; @@ -132,8 +132,17 @@ export class MatrixCommandHandler { we do this by assuming that guildId is parsed first, and split the / off and then pass that on to channelId, if applicable */ - let guildIdRemainder: string | undefined = undefined; + let guildIdRemainder: string | undefined; const parameters: ICommandParameters = { + channelId: { + description: "The ID of a channel on discord", + get: async (s) => { + if (!s && guildIdRemainder) { + return guildIdRemainder; + } + return s; + }, + }, guildId: { description: "The ID of a guild/server on discord", get: async (s) => { @@ -145,18 +154,9 @@ export class MatrixCommandHandler { return parts[0]; }, }, - channelId: { - description: "The ID of a channel on discord", - get: async (s) => { - if (!s && guildIdRemainder) { - return guildIdRemainder; - } - return s; - }, - }, }; - const permissionCheck: ICommandPermissonCheck = async (permission) => { + const permissionCheck: CommandPermissonCheck = async (permission) => { if (permission.selfService && !this.config.bridge.enableSelfServiceBridging) { return "The owner of this bridge does not permit self-service bridging."; } diff --git a/src/matrixtypes.ts b/src/matrixtypes.ts index f71d7debdce3c15efe449f2208264a2efa44a004..7535b59a89063a02e1f1946321550287f483dd94 100644 --- a/src/matrixtypes.ts +++ b/src/matrixtypes.ts @@ -42,7 +42,7 @@ export interface IMatrixEvent { unsigned?: any; // tslint:disable-line no-any origin_server_ts?: number; users?: any; // tslint:disable-line no-any - users_default?: any // tslint:disable-line no-any + users_default?: any; // tslint:disable-line no-any notifications?: any; // tslint:disable-line no-any } diff --git a/src/util.ts b/src/util.ts index 8df0d180186d03a927dad2ba02208dd65a8422d5..80dc32ca65eeae9bd19dd62ae667e1e511c2f815 100644 --- a/src/util.ts +++ b/src/util.ts @@ -32,11 +32,11 @@ const log = new Log("Util"); type PERMISSIONTYPES = any; // tslint:disable-line no-any export interface ICommandAction { - params: string[]; description?: string; + help?: string; + params: string[]; permission?: PERMISSIONTYPES; run(params: any): Promise<any>; // tslint:disable-line no-any - help?: string; } export interface ICommandActions { @@ -52,9 +52,7 @@ export interface ICommandParameters { [index: string]: ICommandParameter; } -export interface ICommandPermissonCheck { - (permission: PERMISSIONTYPES): Promise<boolean | string>; -} +export type CommandPermissonCheck = (permission: PERMISSIONTYPES) => Promise<boolean | string>; export interface IPatternMap { [index: string]: string; @@ -242,7 +240,7 @@ export class Util { actions: ICommandActions, parameters: ICommandParameters, args: string[], - permissionCheck?: ICommandPermissonCheck, + permissionCheck?: CommandPermissonCheck, ): Promise<string> { let reply = ""; if (args[0]) { @@ -298,7 +296,7 @@ export class Util { msg: string, actions: ICommandActions, parameters: ICommandParameters, - permissionCheck?: ICommandPermissonCheck, + permissionCheck?: CommandPermissonCheck, ): Promise<string> { const {command, args} = Util.MsgToArgs(msg, prefix); @@ -316,7 +314,8 @@ export class Util { return `**ERROR:** ${permCheck}`; } if (!permCheck) { - return `**ERROR:** insufficiant permissions to use this command! Try \`${prefix} help\` to see all available commands`; + return `**ERROR:** insufficiant permissions to use this command! ` + + `Try \`${prefix} help\` to see all available commands`; } } if (action.params.length === 1) { diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index 6cf8e29f9642534d52a1121cddad377f4dc148e8..09088f4ceaa59f9ba5af3472b0aa73dfff3efe6e 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -1,5 +1,5 @@ /* -Copyright 2017, 2018 matrix-appservice-discord +Copyright 2017 - 2019 matrix-appservice-discord Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/test/test_discordcommandhandler.ts b/test/test_discordcommandhandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb4b725130353a170934152abd2a0a60eca986f6 --- /dev/null +++ b/test/test_discordcommandhandler.ts @@ -0,0 +1,162 @@ +/* +Copyright 2019 matrix-appservice-discord + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import * as Chai from "chai"; +import * as Proxyquire from "proxyquire"; + +import { DiscordCommandHandler } from "../src/discordcommandhandler"; +import { MockChannel } from "./mocks/channel"; +import { MockMember } from "./mocks/member"; +import { MockGuild } from "./mocks/guild"; +import { Util } from "../src/util"; + +// we are a test file and thus need those +/* tslint:disable:no-unused-expression max-file-line-count no-any */ + +const expect = Chai.expect; + +let USERSJOINED = 0; +let USERSKICKED = 0; +let USERSBANNED = 0; +let USERSUNBANNED = 0; +let MESSAGESENT: any = {}; +function createCH(opts: any = {}) { + USERSJOINED = 0; + USERSKICKED = 0; + USERSBANNED = 0; + USERSUNBANNED = 0; + MESSAGESENT = {}; + const bridge = { + getIntent: () => { + return { + ban: async () => { USERSBANNED++; }, + getEvent: () => ({ content: { } }), + join: () => { USERSJOINED++; }, + kick: async () => { USERSKICKED++; }, + leave: () => { }, + sendMessage: async (roomId, content) => { MESSAGESENT = content; return content; }, + unban: async () => { USERSUNBANNED++; }, + }; + }, + }; + const cs = { + GetRoomIdsFromChannel: async (chan) => { + return [`#${chan.id}:localhost`]; + }, + }; + const discord = { + ChannelSyncroniser: cs, + }; + const discordCommandHndlr = (Proxyquire("../src/discordcommandhandler", { + "./util": { + Util: { + GetMxidFromName: () => { + return "@123456:localhost"; + }, + ParseCommand: Util.ParseCommand, + }, + }, + })).DiscordCommandHandler; + return new discordCommandHndlr(bridge as any, discord as any); +} + +describe("DiscordCommandHandler", () => { + it("will kick a member", async () => { + const handler: any = createCH(); + const channel = new MockChannel("123"); + const guild = new MockGuild("456", [channel]); + channel.guild = guild; + const member: any = new MockMember("123456", "blah"); + member.hasPermission = () => { + return true; + }; + const message = { + channel, + content: "!matrix kick someuser", + member, + }; + await handler.Process(message); + expect(USERSKICKED).equals(1); + }); + it("will kick a member in all guild rooms", async () => { + const handler: any = createCH(); + const channel = new MockChannel("123"); + const guild = new MockGuild("456", [channel, (new MockChannel("456"))]); + channel.guild = guild; + const member: any = new MockMember("123456", "blah"); + member.hasPermission = () => { + return true; + }; + const message = { + channel, + content: "!matrix kick someuser", + member, + }; + await handler.Process(message); + // tslint:disable-next-line:no-magic-numbers + expect(USERSKICKED).equals(2); + }); + it("will deny permission", async () => { + const handler: any = createCH(); + const channel = new MockChannel("123"); + const guild = new MockGuild("456", [channel]); + channel.guild = guild; + const member: any = new MockMember("123456", "blah"); + member.hasPermission = () => { + return false; + }; + const message = { + channel, + content: "!matrix kick someuser", + member, + }; + await handler.Process(message); + expect(USERSKICKED).equals(0); + }); + it("will ban a member", async () => { + const handler: any = createCH(); + const channel = new MockChannel("123"); + const guild = new MockGuild("456", [channel]); + channel.guild = guild; + const member: any = new MockMember("123456", "blah"); + member.hasPermission = () => { + return true; + }; + const message = { + channel, + content: "!matrix ban someuser", + member, + }; + await handler.Process(message); + expect(USERSBANNED).equals(1); + }); + it("will unban a member", async () => { + const handler: any = createCH(); + const channel = new MockChannel("123"); + const guild = new MockGuild("456", [channel]); + channel.guild = guild; + const member: any = new MockMember("123456", "blah"); + member.hasPermission = () => { + return true; + }; + const message = { + channel, + content: "!matrix unban someuser", + member, + }; + await handler.Process(message); + expect(USERSUNBANNED).equals(1); + }); +}); diff --git a/test/test_matrixcommandhandler.ts b/test/test_matrixcommandhandler.ts index 3468a4b3a69df899609f97b80d319db940a500ae..70359d79ea92db5e15064a97fed075edc9589f11 100644 --- a/test/test_matrixcommandhandler.ts +++ b/test/test_matrixcommandhandler.ts @@ -1,3 +1,19 @@ +/* +Copyright 2019 matrix-appservice-discord + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import * as Chai from "chai"; import { Util } from "../src/util"; import { DiscordBridgeConfig } from "../src/config"; @@ -99,19 +115,19 @@ function createCH(opts: any = {}) { }, GetBotLink: Util.GetBotLink, ParseCommand: Util.ParseCommand, - } - } + }, + }, })).MatrixCommandHandler; return new MatrixCommandHndl(bot as any, bridge, config); } -function createEvent(msg: string, room?: string, user_id?: string) { +function createEvent(msg: string, room?: string, userId?: string) { return { content: { body: msg, }, room_id: room ? room : "!123:localhost", - sender: user_id, + sender: userId, }; } @@ -133,14 +149,16 @@ describe("MatrixCommandHandler", () => { it("should warn if self service is disabled", async () => { const handler: any = createCH({disableSS: true}); await handler.Process(createEvent("!discord bridge"), createContext()); - expect(MESSAGESENT.body).to.equal("**ERROR:** The owner of this bridge does not permit self-service bridging."); + expect(MESSAGESENT.body).to.equal("**ERROR:** The owner of this bridge does " + + "not permit self-service bridging."); }); it("should warn if user is not powerful enough", async () => { const handler: any = createCH({ power: false, }); await handler.Process(createEvent("!discord bridge"), createContext()); - expect(MESSAGESENT.body).to.equal("**ERROR:** insufficiant permissions to use this command! Try `!discord help` to see all available commands"); + expect(MESSAGESENT.body).to.equal("**ERROR:** insufficiant permissions to use this " + + "command! Try `!discord help` to see all available commands"); }); describe("!discord bridge", () => { it("will bridge a new room, and ask for permissions", async () => { @@ -184,7 +202,7 @@ describe("MatrixCommandHandler", () => { describe("!discord unbridge", () => { it("will unbridge", async () => { const handler: any = createCH(); - await handler.Process(createEvent("!discord unbridge"), createContext({data:{plumbed:true}})); + await handler.Process(createEvent("!discord unbridge"), createContext({data: {plumbed: true}})); expect(MESSAGESENT.body).equals("This room has been unbridged"); }); it("will not unbridge if a link does not exist", async () => { @@ -194,14 +212,14 @@ describe("MatrixCommandHandler", () => { }); it("will not unbridge non-plumbed rooms", async () => { const handler: any = createCH(); - await handler.Process(createEvent("!discord unbridge"), createContext({data:{plumbed:false}})); + await handler.Process(createEvent("!discord unbridge"), createContext({data: {plumbed: false}})); expect(MESSAGESENT.body).equals("This room cannot be unbridged."); }); it("will show error if unbridge fails", async () => { const handler: any = createCH({ failUnbridge: true, }); - await handler.Process(createEvent("!discord unbridge"), createContext({data:{plumbed:true}})); + await handler.Process(createEvent("!discord unbridge"), createContext({data: {plumbed: true}})); expect(MESSAGESENT.body).to.contain("There was an error unbridging this room."); }); }); diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts index eefa16106ec3cb60dc45886d4726299950a33e08..a5d109b5b78402eea68858b1d5615683e5a51d49 100644 --- a/test/test_matrixeventprocessor.ts +++ b/test/test_matrixeventprocessor.ts @@ -89,7 +89,6 @@ let STATE_EVENT_MSG = ""; let USERSYNC_HANDLED = false; let MESSAGE_PROCCESS = ""; let KICKBAN_HANDLED = false; -let COMMAND_PROCESSED = false; function createMatrixEventProcessor(): MatrixEventProcessor { USERSYNC_HANDLED = false; diff --git a/test/test_matrixroomhandler.ts b/test/test_matrixroomhandler.ts index 4c5de12bb552fc923823a55044857f3531b394ad..d8fa8d04ab87923b35586e366cb775d55d3fe894 100644 --- a/test/test_matrixroomhandler.ts +++ b/test/test_matrixroomhandler.ts @@ -1,5 +1,5 @@ /* -Copyright 2018 matrix-appservice-discord +Copyright 2018, 2019 matrix-appservice-discord Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ limitations under the License. import * as Chai from "chai"; import * as Proxyquire from "proxyquire"; -import {DiscordBridgeConfig} from "../src/config"; -import {MockChannel} from "./mocks/channel"; -import {MockMember} from "./mocks/member"; -import {MockGuild} from "./mocks/guild"; +import { DiscordBridgeConfig } from "../src/config"; +import { MockChannel } from "./mocks/channel"; +import { MockMember } from "./mocks/member"; +import { MockGuild } from "./mocks/guild"; import { Util } from "../src/util"; // we are a test file and thus need those @@ -54,6 +54,7 @@ function createRH(opts: any = {}) { USERSKICKED = 0; USERSBANNED = 0; USERSUNBANNED = 0; + MESSAGESENT = {}; USERSYNC_HANDLED = false; KICKBAN_HANDLED = false; MESSAGE_PROCCESS = ""; @@ -354,92 +355,4 @@ describe("MatrixRoomHandler", () => { expect(roomOpts.creationOpts).to.exist; }); }); - describe("HandleDiscordCommand", () => { - it("will kick a member", async () => { - const handler: any = createRH({}); - const channel = new MockChannel("123"); - const guild = new MockGuild("456", [channel]); - channel.guild = guild; - const member: any = new MockMember("123456", "blah"); - member.hasPermission = () => { - return true; - }; - const message = { - channel, - content: "!matrix kick someuser", - member, - }; - await handler.HandleDiscordCommand(message); - expect(USERSKICKED).equals(1); - }); - it("will kick a member in all guild rooms", async () => { - const handler: any = createRH({}); - const channel = new MockChannel("123"); - const guild = new MockGuild("456", [channel, (new MockChannel("456"))]); - channel.guild = guild; - const member: any = new MockMember("123456", "blah"); - member.hasPermission = () => { - return true; - }; - const message = { - channel, - content: "!matrix kick someuser", - member, - }; - await handler.HandleDiscordCommand(message); - // tslint:disable-next-line:no-magic-numbers - expect(USERSKICKED).equals(2); - }); - it("will deny permission", async () => { - const handler: any = createRH({}); - const channel = new MockChannel("123"); - const guild = new MockGuild("456", [channel]); - channel.guild = guild; - const member: any = new MockMember("123456", "blah"); - member.hasPermission = () => { - return false; - }; - const message = { - channel, - content: "!matrix kick someuser", - member, - }; - await handler.HandleDiscordCommand(message); - expect(USERSKICKED).equals(0); - }); - it("will ban a member", async () => { - const handler: any = createRH({}); - const channel = new MockChannel("123"); - const guild = new MockGuild("456", [channel]); - channel.guild = guild; - const member: any = new MockMember("123456", "blah"); - member.hasPermission = () => { - return true; - }; - const message = { - channel, - content: "!matrix ban someuser", - member, - }; - await handler.HandleDiscordCommand(message); - expect(USERSBANNED).equals(1); - }); - it("will unban a member", async () => { - const handler: any = createRH({}); - const channel = new MockChannel("123"); - const guild = new MockGuild("456", [channel]); - channel.guild = guild; - const member: any = new MockMember("123456", "blah"); - member.hasPermission = () => { - return true; - }; - const message = { - channel, - content: "!matrix unban someuser", - member, - }; - await handler.HandleDiscordCommand(message); - expect(USERSUNBANNED).equals(1); - }); - }); }); diff --git a/test/test_util.ts b/test/test_util.ts index 70c8d7b57a7c8572d45155ce9c114f4e18973c4a..5f294efe3f2b63137e7494bbcc39366f3feb4115 100644 --- a/test/test_util.ts +++ b/test/test_util.ts @@ -1,5 +1,5 @@ /* -Copyright 2018 matrix-appservice-discord +Copyright 2018, 2019 matrix-appservice-discord Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.