From aa9191bc342c41a87a22dad037d34df7de305988 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Tue, 16 Aug 2022 15:20:26 +0200 Subject: [PATCH] Disable some slash commands in LocalRoom (#9192) --- src/SlashCommands.tsx | 39 +++++++-- test/SlashCommands-test.tsx | 162 +++++++++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 8 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 9d3047a275..5a5f59498f 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -69,6 +69,7 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import VoipUserMapper from './VoipUserMapper'; import { htmlSerializeFromMdIfNeeded } from './editor/serialize'; import { leaveRoomBehaviour } from "./utils/leave-behaviour"; +import { isLocalRoom } from './utils/localRoom/isLocalRoom'; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -206,6 +207,12 @@ function successSync(value: any) { return success(Promise.resolve(value)); } +const isCurrentLocalRoom = (): boolean => { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(RoomViewStore.instance.getRoomId()); + return isLocalRoom(room); +}; + /* Disable the "unexpected this" error for these commands - all of the run * functions are called with `this` bound to the Command instance. */ @@ -297,6 +304,7 @@ export const Commands = [ command: 'upgraderoom', args: '', description: _td('Upgrades a room to a new version'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { const cli = MatrixClientPeg.get(); @@ -380,6 +388,7 @@ export const Commands = [ aliases: ['roomnick'], args: '', description: _td('Changes your display nickname in the current room only'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { const cli = MatrixClientPeg.get(); @@ -399,6 +408,7 @@ export const Commands = [ command: 'roomavatar', args: '[]', description: _td('Changes the avatar of the current room'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { let promise = Promise.resolve(args); if (!args) { @@ -417,6 +427,7 @@ export const Commands = [ command: 'myroomavatar', args: '[]', description: _td('Changes your avatar in this current room only'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); @@ -462,6 +473,7 @@ export const Commands = [ command: 'topic', args: '[]', description: _td('Gets or sets the room topic'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const cli = MatrixClientPeg.get(); if (args) { @@ -498,6 +510,7 @@ export const Commands = [ command: 'roomname', args: '', description: _td('Sets the room name'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { return success(MatrixClientPeg.get().setRoomName(roomId, args)); @@ -512,7 +525,7 @@ export const Commands = [ args: ' []', description: _td('Invites user with given id to current room'), analyticsName: "Invite", - isEnabled: () => shouldShowComponent(UIComponent.InviteUsers), + isEnabled: () => !isCurrentLocalRoom() && shouldShowComponent(UIComponent.InviteUsers), runFn: function(roomId, args) { if (args) { const [address, reason] = args.split(/\s+(.+)/); @@ -694,6 +707,7 @@ export const Commands = [ args: '[]', description: _td('Leave room'), analyticsName: "Part", + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const cli = MatrixClientPeg.get(); @@ -746,6 +760,7 @@ export const Commands = [ aliases: ["kick"], args: ' [reason]', description: _td('Removes user with given id from this room'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { const matches = args.match(/^(\S+?)( +(.*))?$/); @@ -762,6 +777,7 @@ export const Commands = [ command: 'ban', args: ' [reason]', description: _td('Bans user with given id'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { const matches = args.match(/^(\S+?)( +(.*))?$/); @@ -778,6 +794,7 @@ export const Commands = [ command: 'unban', args: '', description: _td('Unbans user with given ID'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { if (args) { const matches = args.match(/^(\S+)$/); @@ -857,7 +874,8 @@ export const Commands = [ isEnabled(): boolean { const cli = MatrixClientPeg.get(); const room = cli.getRoom(RoomViewStore.instance.getRoomId()); - return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()); + return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) + && !isLocalRoom(room); }, runFn: function(roomId, args) { if (args) { @@ -897,7 +915,8 @@ export const Commands = [ isEnabled(): boolean { const cli = MatrixClientPeg.get(); const room = cli.getRoom(RoomViewStore.instance.getRoomId()); - return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()); + return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) + && !isLocalRoom(room); }, runFn: function(roomId, args) { if (args) { @@ -936,7 +955,9 @@ export const Commands = [ command: 'addwidget', args: '', description: _td('Adds a custom widget by URL to the room'), - isEnabled: () => SettingsStore.getValue(UIFeature.Widgets) && shouldShowComponent(UIComponent.AddIntegrations), + isEnabled: () => SettingsStore.getValue(UIFeature.Widgets) + && shouldShowComponent(UIComponent.AddIntegrations) + && !isCurrentLocalRoom(), runFn: function(roomId, widgetUrl) { if (!widgetUrl) { return reject(newTranslatableError("Please supply a widget URL or embed code")); @@ -1059,6 +1080,7 @@ export const Commands = [ new Command({ command: 'discardsession', description: _td('Forces the current outbound group session in an encrypted room to be discarded'), + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId) { try { MatrixClientPeg.get().forceDiscardSession(roomId); @@ -1074,7 +1096,7 @@ export const Commands = [ command: 'remakeolm', description: _td('Developer command: Discards the current outbound group session and sets up new Olm sessions'), isEnabled: () => { - return SettingsStore.getValue("developerMode"); + return SettingsStore.getValue("developerMode") && !isCurrentLocalRoom(); }, runFn: (roomId) => { try { @@ -1125,6 +1147,7 @@ export const Commands = [ command: "whois", description: _td("Displays information about a user"), args: "", + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, userId) { if (!userId || !userId.startsWith("@") || !userId.includes(":")) { return reject(this.getUsage()); @@ -1160,7 +1183,7 @@ export const Commands = [ description: _td("Switches to this room's virtual room, if it has one"), category: CommandCategories.advanced, isEnabled(): boolean { - return CallHandler.instance.getSupportsVirtualRooms(); + return CallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom(); }, runFn: (roomId) => { return success((async () => { @@ -1244,6 +1267,7 @@ export const Commands = [ command: "holdcall", description: _td("Places the call in the current room on hold"), category: CommandCategories.other, + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const call = CallHandler.instance.getCallForRoom(roomId); if (!call) { @@ -1258,6 +1282,7 @@ export const Commands = [ command: "unholdcall", description: _td("Takes the call in the current room off hold"), category: CommandCategories.other, + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const call = CallHandler.instance.getCallForRoom(roomId); if (!call) { @@ -1272,6 +1297,7 @@ export const Commands = [ command: "converttodm", description: _td("Converts the room to a DM"), category: CommandCategories.other, + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const room = MatrixClientPeg.get().getRoom(roomId); return success(guessAndSetDMRoom(room, true)); @@ -1282,6 +1308,7 @@ export const Commands = [ command: "converttoroom", description: _td("Converts the DM to a room"), category: CommandCategories.other, + isEnabled: () => !isCurrentLocalRoom(), runFn: function(roomId, args) { const room = MatrixClientPeg.get().getRoom(roomId); return success(guessAndSetDMRoom(room, false)); diff --git a/test/SlashCommands-test.tsx b/test/SlashCommands-test.tsx index f2b15fb729..09d8e3c587 100644 --- a/test/SlashCommands-test.tsx +++ b/test/SlashCommands-test.tsx @@ -14,18 +14,53 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient } from 'matrix-js-sdk/src/matrix'; +import { MatrixClient, Room } from 'matrix-js-sdk/src/matrix'; +import { mocked } from 'jest-mock'; -import { getCommand } from '../src/SlashCommands'; +import { Command, Commands, getCommand } from '../src/SlashCommands'; import { createTestClient } from './test-utils'; import { MatrixClientPeg } from '../src/MatrixClientPeg'; +import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom'; +import { RoomViewStore } from '../src/stores/RoomViewStore'; +import SettingsStore from '../src/settings/SettingsStore'; +import CallHandler from '../src/CallHandler'; describe('SlashCommands', () => { let client: MatrixClient; + const roomId = "!room:example.com"; + let room: Room; + const localRoomId = LOCAL_ROOM_ID_PREFIX + "test"; + let localRoom: LocalRoom; + let command: Command; + + const findCommand = (cmd: string): Command => { + return Commands.find((command: Command) => command.command === cmd); + }; + + const setCurrentRoom = (): void => { + mocked(RoomViewStore.instance.getRoomId).mockReturnValue(roomId); + mocked(client.getRoom).mockImplementation((rId: string): Room => { + if (rId === roomId) return room; + }); + }; + + const setCurrentLocalRoon = (): void => { + mocked(RoomViewStore.instance.getRoomId).mockReturnValue(localRoomId); + mocked(client.getRoom).mockImplementation((rId: string): Room => { + if (rId === localRoomId) return localRoom; + }); + }; beforeEach(() => { + jest.clearAllMocks(); + client = createTestClient(); jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(client); + + room = new Room(roomId, client, client.getUserId()); + localRoom = new LocalRoom(localRoomId, client, client.getUserId()); + + jest.spyOn(RoomViewStore.instance, "getRoomId"); }); describe('/topic', () => { @@ -37,4 +72,127 @@ describe('SlashCommands', () => { expect(client.setRoomTopic).toHaveBeenCalledWith("room-id", "pizza", undefined); }); }); + + describe.each([ + ["upgraderoom"], + ["myroomnick"], + ["roomavatar"], + ["myroomavatar"], + ["topic"], + ["roomname"], + ["invite"], + ["part"], + ["remove"], + ["ban"], + ["unban"], + ["op"], + ["deop"], + ["addwidget"], + ["discardsession"], + ["whois"], + ["holdcall"], + ["unholdcall"], + ["converttodm"], + ["converttoroom"], + ])("/%s", (commandName: string) => { + beforeEach(() => { + command = findCommand(commandName); + }); + + describe("isEnabled", () => { + it("should return true for Room", () => { + setCurrentRoom(); + expect(command.isEnabled()).toBe(true); + }); + + it("should return false for LocalRoom", () => { + setCurrentLocalRoon(); + expect(command.isEnabled()).toBe(false); + }); + }); + }); + + describe("/tovirtual", () => { + beforeEach(() => { + command = findCommand("tovirtual"); + }); + + describe("isEnabled", () => { + describe("when virtual rooms are supported", () => { + beforeEach(() => { + jest.spyOn(CallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(true); + }); + + it("should return true for Room", () => { + setCurrentRoom(); + expect(command.isEnabled()).toBe(true); + }); + + it("should return false for LocalRoom", () => { + setCurrentLocalRoon(); + expect(command.isEnabled()).toBe(false); + }); + }); + + describe("when virtual rooms are not supported", () => { + beforeEach(() => { + jest.spyOn(CallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(false); + }); + + it("should return false for Room", () => { + setCurrentRoom(); + expect(command.isEnabled()).toBe(false); + }); + + it("should return false for LocalRoom", () => { + setCurrentLocalRoon(); + expect(command.isEnabled()).toBe(false); + }); + }); + }); + }); + + describe("/remakeolm", () => { + beforeEach(() => { + command = findCommand("remakeolm"); + }); + + describe("isEnabled", () => { + describe("when developer mode is enabled", () => { + beforeEach(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => { + if (settingName === "developerMode") return true; + }); + }); + + it("should return true for Room", () => { + setCurrentRoom(); + expect(command.isEnabled()).toBe(true); + }); + + it("should return false for LocalRoom", () => { + setCurrentLocalRoon(); + expect(command.isEnabled()).toBe(false); + }); + }); + + describe("when developer mode is not enabled", () => { + beforeEach(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => { + if (settingName === "developerMode") return false; + }); + }); + + it("should return false for Room", () => { + setCurrentRoom(); + expect(command.isEnabled()).toBe(false); + }); + + it("should return false for LocalRoom", () => { + setCurrentLocalRoon(); + expect(command.isEnabled()).toBe(false); + }); + }); + }); + }); });