From 8c2b910c03e1e2dc26cba810bc74c0ff97459ce7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Mar 2020 13:59:08 +0100 Subject: [PATCH 1/4] rework SlashCommands to better expose aliases Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{SlashCommands.js => SlashCommands.tsx} | 272 ++++++++---------- src/autocomplete/CommandProvider.js | 46 +-- .../views/dialogs/SlashCommandHelpDialog.js | 6 +- 3 files changed, 156 insertions(+), 168 deletions(-) rename src/{SlashCommands.js => SlashCommands.tsx} (91%) diff --git a/src/SlashCommands.js b/src/SlashCommands.tsx similarity index 91% rename from src/SlashCommands.js rename to src/SlashCommands.tsx index d306978f781..e69afbde48f 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.tsx @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2018 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +18,8 @@ limitations under the License. */ -import React from 'react'; +import * as React from 'react'; + import {MatrixClientPeg} from './MatrixClientPeg'; import dis from './dispatcher'; import * as sdk from './index'; @@ -34,11 +36,16 @@ import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/I import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks"; import {inviteUsersToRoom} from "./RoomInvite"; -const singleMxcUpload = async () => { +// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 +interface HTMLInputEvent extends Event { + target: HTMLInputElement & EventTarget; +} + +const singleMxcUpload = async (): Promise => { return new Promise((resolve) => { const fileSelector = document.createElement('input'); fileSelector.setAttribute('type', 'file'); - fileSelector.onchange = (ev) => { + fileSelector.onchange = (ev: HTMLInputEvent) => { const file = ev.target.files[0]; const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog"); @@ -62,9 +69,36 @@ export const CommandCategories = { "other": _td("Other"), }; +type RunFn = ((roomId: string, args: string, cmd: string) => {error: any} | {promise: Promise}); + class Command { - constructor({name, args='', description, runFn, category=CommandCategories.other, hideCompletionAfterSpace=false}) { - this.command = '/' + name; + command: string; + aliases: string[]; + args: undefined | string; + description: string; + runFn: undefined | RunFn; + category: string; + hideCompletionAfterSpace: boolean; + + constructor({ + command, + aliases=[], + args='', + description, + runFn=undefined, + category=CommandCategories.other, + hideCompletionAfterSpace=false, + }: { + command: string; + aliases?: string[]; + args?: string; + description: string; + runFn?: RunFn; + category: string; + hideCompletionAfterSpace?: boolean; + }) { + this.command = command; + this.aliases = aliases; this.args = args; this.description = description; this.runFn = runFn; @@ -73,17 +107,17 @@ class Command { } getCommand() { - return this.command; + return `/${this.command}`; } getCommandWithArgs() { return this.getCommand() + " " + this.args; } - run(roomId, args) { + run(roomId: string, args: string, cmd: string) { // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` if (!this.runFn) return; - return this.runFn.bind(this)(roomId, args); + return this.runFn.bind(this)(roomId, args, cmd); } getUsage() { @@ -95,7 +129,7 @@ function reject(error) { return {error}; } -function success(promise) { +function success(promise?: Promise) { return {promise}; } @@ -103,11 +137,9 @@ function success(promise) { * functions are called with `this` bound to the Command instance. */ -/* eslint-disable babel/no-invalid-this */ - -export const CommandMap = { - shrug: new Command({ - name: 'shrug', +export const Commands = [ + new Command({ + command: 'shrug', args: '', description: _td('Prepends ¯\\_(ツ)_/¯ to a plain-text message'), runFn: function(roomId, args) { @@ -119,8 +151,8 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - plain: new Command({ - name: 'plain', + new Command({ + command: 'plain', args: '', description: _td('Sends a message as plain text, without interpreting it as markdown'), runFn: function(roomId, messages) { @@ -128,11 +160,11 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - ddg: new Command({ - name: 'ddg', + new Command({ + command: 'ddg', args: '', description: _td('Searches DuckDuckGo for results'), - runFn: function(roomId, args) { + runFn: function() { const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); // TODO Don't explain this away, actually show a search UI here. Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, { @@ -144,9 +176,8 @@ export const CommandMap = { category: CommandCategories.actions, hideCompletionAfterSpace: true, }), - - upgraderoom: new Command({ - name: 'upgraderoom', + new Command({ + command: 'upgraderoom', args: '', description: _td('Upgrades a room to a new version'), runFn: function(roomId, args) { @@ -215,9 +246,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - nick: new Command({ - name: 'nick', + new Command({ + command: 'nick', args: '', description: _td('Changes your display nickname'), runFn: function(roomId, args) { @@ -228,9 +258,9 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myroomnick: new Command({ - name: 'myroomnick', + new Command({ + command: 'myroomnick', + aliases: ['roomnick'], args: '', description: _td('Changes your display nickname in the current room only'), runFn: function(roomId, args) { @@ -239,7 +269,7 @@ export const CommandMap = { const ev = cli.getRoom(roomId).currentState.getStateEvents('m.room.member', cli.getUserId()); const content = { ...ev ? ev.getContent() : { membership: 'join' }, - displayname: args, + displaycommand: args, }; return success(cli.sendStateEvent(roomId, 'm.room.member', content, cli.getUserId())); } @@ -247,9 +277,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - roomavatar: new Command({ - name: 'roomavatar', + new Command({ + command: 'roomavatar', args: '[]', description: _td('Changes the avatar of the current room'), runFn: function(roomId, args) { @@ -265,9 +294,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myroomavatar: new Command({ - name: 'myroomavatar', + new Command({ + command: 'myroomavatar', args: '[]', description: _td('Changes your avatar in this current room only'), runFn: function(roomId, args) { @@ -292,9 +320,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myavatar: new Command({ - name: 'myavatar', + new Command({ + command: 'myavatar', args: '[]', description: _td('Changes your avatar in all rooms'), runFn: function(roomId, args) { @@ -310,9 +337,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - topic: new Command({ - name: 'topic', + new Command({ + command: 'topic', args: '[]', description: _td('Gets or sets the room topic'), runFn: function(roomId, args) { @@ -336,9 +362,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - roomname: new Command({ - name: 'roomname', + new Command({ + command: 'roomname', args: '', description: _td('Sets the room name'), runFn: function(roomId, args) { @@ -349,9 +374,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - invite: new Command({ - name: 'invite', + new Command({ + command: 'invite', args: '', description: _td('Invites user with given id to current room'), runFn: function(roomId, args) { @@ -390,7 +414,7 @@ export const CommandMap = { } } const inviter = new MultiInviter(roomId); - return success(finished.then(([useDefault] = []) => { + return success(finished.then(([useDefault]: any) => { if (useDefault) { useDefaultIdentityServer(); } else if (useDefault === false) { @@ -408,9 +432,9 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - join: new Command({ - name: 'join', + new Command({ + command: 'join', + aliases: ['j', 'goto'], args: '', description: _td('Joins room with given alias'), runFn: function(roomId, args) { @@ -521,9 +545,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - part: new Command({ - name: 'part', + new Command({ + command: 'part', args: '[]', description: _td('Leave room'), runFn: function(roomId, args) { @@ -569,9 +592,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - kick: new Command({ - name: 'kick', + new Command({ + command: 'kick', args: ' [reason]', description: _td('Kicks user with given id'), runFn: function(roomId, args) { @@ -585,10 +607,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Ban a user from the room with an optional reason - ban: new Command({ - name: 'ban', + new Command({ + command: 'ban', args: ' [reason]', description: _td('Bans user with given id'), runFn: function(roomId, args) { @@ -602,10 +622,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Unban a user from ythe room - unban: new Command({ - name: 'unban', + new Command({ + command: 'unban', args: '', description: _td('Unbans user with given ID'), runFn: function(roomId, args) { @@ -620,9 +638,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - ignore: new Command({ - name: 'ignore', + new Command({ + command: 'ignore', args: '', description: _td('Ignores a user, hiding their messages from you'), runFn: function(roomId, args) { @@ -651,9 +668,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - unignore: new Command({ - name: 'unignore', + new Command({ + command: 'unignore', args: '', description: _td('Stops ignoring a user, showing their messages going forward'), runFn: function(roomId, args) { @@ -683,10 +699,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - // Define the power level of a user - op: new Command({ - name: 'op', + new Command({ + command: 'op', args: ' []', description: _td('Define the power level of a user'), runFn: function(roomId, args) { @@ -712,10 +726,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Reset the power level of a user - deop: new Command({ - name: 'deop', + new Command({ + command: 'deop', args: '', description: _td('Deops user with given id'), runFn: function(roomId, args) { @@ -734,9 +746,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - devtools: new Command({ - name: 'devtools', + new Command({ + command: 'devtools', description: _td('Opens the Developer Tools dialog'), runFn: function(roomId) { const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog'); @@ -745,9 +756,8 @@ export const CommandMap = { }, category: CommandCategories.advanced, }), - - addwidget: new Command({ - name: 'addwidget', + new Command({ + command: 'addwidget', args: '', description: _td('Adds a custom widget by URL to the room'), runFn: function(roomId, args) { @@ -766,10 +776,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Verify a user, device, and pubkey tuple - verify: new Command({ - name: 'verify', + new Command({ + command: 'verify', args: ' ', description: _td('Verifies a user, session, and pubkey tuple'), runFn: function(roomId, args) { @@ -834,20 +842,9 @@ export const CommandMap = { }, category: CommandCategories.advanced, }), - - // Command definitions for autocompletion ONLY: - - // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes - me: new Command({ - name: 'me', - args: '', - description: _td('Displays action'), - category: CommandCategories.messages, - hideCompletionAfterSpace: true, - }), - - discardsession: new Command({ - name: 'discardsession', + new Command({ + command: 'discardsession', + aliases: ['newballsplease'], description: _td('Forces the current outbound group session in an encrypted room to be discarded'), runFn: function(roomId) { try { @@ -859,9 +856,8 @@ export const CommandMap = { }, category: CommandCategories.advanced, }), - - rainbow: new Command({ - name: "rainbow", + new Command({ + command: "rainbow", description: _td("Sends the given message coloured as a rainbow"), args: '', runFn: function(roomId, args) { @@ -870,9 +866,8 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - - rainbowme: new Command({ - name: "rainbowme", + new Command({ + command: "rainbowme", description: _td("Sends the given emote coloured as a rainbow"), args: '', runFn: function(roomId, args) { @@ -881,9 +876,8 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - - help: new Command({ - name: "help", + new Command({ + command: "help", description: _td("Displays list of commands with usages and descriptions"), runFn: function() { const SlashCommandHelpDialog = sdk.getComponent('dialogs.SlashCommandHelpDialog'); @@ -894,36 +888,25 @@ export const CommandMap = { category: CommandCategories.advanced, }), - whois: new Command({ - name: "whois", - description: _td("Displays information about a user"), - args: '', - runFn: function(roomId, userId) { - if (!userId || !userId.startsWith("@") || !userId.includes(":")) { - return reject(this.getUsage()); - } - - const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); - - dis.dispatch({ - action: 'view_user', - member: member || {userId}, - }); - return success(); - }, - category: CommandCategories.advanced, + // Command definitions for autocompletion ONLY: + // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes + new Command({ + command: 'me', + args: '', + description: _td('Displays action'), + category: CommandCategories.messages, + hideCompletionAfterSpace: true, }), -}; -/* eslint-enable babel/no-invalid-this */ - - -// helpful aliases -const aliases = { - j: "join", - newballsplease: "discardsession", - goto: "join", // because it handles event permalinks magically - roomnick: "myroomnick", -}; +]; + +// build a map from names and aliases to the Command objects. +export const CommandMap = new Map(); +Commands.forEach(cmd => { + CommandMap.set(cmd.command, cmd); + cmd.aliases.forEach(alias => { + CommandMap.set(alias, cmd); + }); +}); /** @@ -950,10 +933,7 @@ export function getCommand(roomId, input) { cmd = input; } - if (aliases[cmd]) { - cmd = aliases[cmd]; - } - if (CommandMap[cmd]) { - return () => CommandMap[cmd].run(roomId, args); + if (CommandMap.has(cmd)) { + return () => CommandMap.get(cmd).run(roomId, args, cmd); } } diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.js index da8fa3ed3c5..0b8af4d6f9b 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.js @@ -23,17 +23,16 @@ import AutocompleteProvider from './AutocompleteProvider'; import QueryMatcher from './QueryMatcher'; import {TextualCompletion} from './Components'; import type {Completion, SelectionRange} from "./Autocompleter"; -import {CommandMap} from '../SlashCommands'; - -const COMMANDS = Object.values(CommandMap); +import {Commands, CommandMap} from '../SlashCommands'; const COMMAND_RE = /(^\/\w*)(?: .*)?/g; export default class CommandProvider extends AutocompleteProvider { constructor() { super(COMMAND_RE); - this.matcher = new QueryMatcher(COMMANDS, { - keys: ['command', 'args', 'description'], + this.matcher = new QueryMatcher(Commands, { + keys: ['command', 'args', 'description'], + funcs: [({aliases}) => aliases.join(" ")], // aliases }); } @@ -46,31 +45,40 @@ export default class CommandProvider extends AutocompleteProvider { if (command[0] !== command[1]) { // The input looks like a command with arguments, perform exact match const name = command[1].substr(1); // strip leading `/` - if (CommandMap[name]) { + if (CommandMap.has(name)) { // some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments - if (CommandMap[name].hideCompletionAfterSpace) return []; - matches = [CommandMap[name]]; + if (CommandMap.get(name).hideCompletionAfterSpace) return []; + matches = [CommandMap.get(name)]; } } else { if (query === '/') { // If they have just entered `/` show everything - matches = COMMANDS; + matches = Commands; } else { // otherwise fuzzy match against all of the fields matches = this.matcher.match(command[1]); } } - return matches.map((result) => ({ - // If the command is the same as the one they entered, we don't want to discard their arguments - completion: result.command === command[1] ? command[0] : (result.command + ' '), - type: "command", - component: , - range, - })); + + return matches.map((result) => { + let completion = result.getCommand() + ' '; + const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]); + // If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments + if (usedAlias || result.getCommand() === command[1]) { + completion = command[0]; + } + + return { + completion, + type: "command", + component: , + range, + }; + }); } getName() { diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.js b/src/components/views/dialogs/SlashCommandHelpDialog.js index 9e48a92ed1e..bae5b37993c 100644 --- a/src/components/views/dialogs/SlashCommandHelpDialog.js +++ b/src/components/views/dialogs/SlashCommandHelpDialog.js @@ -16,14 +16,14 @@ limitations under the License. import React from 'react'; import {_t} from "../../../languageHandler"; -import {CommandCategories, CommandMap} from "../../../SlashCommands"; +import {CommandCategories, Commands} from "../../../SlashCommands"; import * as sdk from "../../../index"; export default ({onFinished}) => { const InfoDialog = sdk.getComponent('dialogs.InfoDialog'); const categories = {}; - Object.values(CommandMap).forEach(cmd => { + Commands.forEach(cmd => { if (!categories[cmd.category]) { categories[cmd.category] = []; } @@ -41,7 +41,7 @@ export default ({onFinished}) => { categories[category].forEach(cmd => { rows.push( - {cmd.command} + {cmd.getCommand()} {cmd.args} {cmd.description} ); From c6d69f3d3fa072052d11f989671d7deea4311a3c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Mar 2020 14:09:10 +0100 Subject: [PATCH 2/4] de-tslint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index e69afbde48f..728f67987a3 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -82,12 +82,12 @@ class Command { constructor({ command, - aliases=[], - args='', + aliases = [], + args = '', description, runFn=undefined, - category=CommandCategories.other, - hideCompletionAfterSpace=false, + category = CommandCategories.other, + hideCompletionAfterSpace = false, }: { command: string; aliases?: string[]; @@ -437,7 +437,7 @@ export const Commands = [ aliases: ['j', 'goto'], args: '', description: _td('Joins room with given alias'), - runFn: function(roomId, args) { + runFn: function(_, args) { if (args) { // Note: we support 2 versions of this command. The first is // the public-facing one for most users and the other is a @@ -710,7 +710,7 @@ export const Commands = [ if (matches) { const userId = matches[1]; if (matches.length === 4 && undefined !== matches[3]) { - powerLevel = parseInt(matches[3]); + powerLevel = parseInt(matches[3], 10); } if (!isNaN(powerLevel)) { const cli = MatrixClientPeg.get(); From 559b8e174ba355c6151157be09d6a7d43de5f904 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Mar 2020 14:13:08 +0100 Subject: [PATCH 3/4] undo accidental changes Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 20 +++++++++++++++++++- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 728f67987a3..8351013a5ad 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -269,7 +269,7 @@ export const Commands = [ const ev = cli.getRoom(roomId).currentState.getStateEvents('m.room.member', cli.getUserId()); const content = { ...ev ? ev.getContent() : { membership: 'join' }, - displaycommand: args, + displayname: args, }; return success(cli.sendStateEvent(roomId, 'm.room.member', content, cli.getUserId())); } @@ -887,6 +887,24 @@ export const Commands = [ }, category: CommandCategories.advanced, }), + new Command({ + command: "whois", + description: _td("Displays information about a user"), + args: "", + runFn: function (roomId, userId) { + if (!userId || !userId.startsWith("@") || !userId.includes(":")) { + return reject(this.getUsage()); + } + + const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); + dis.dispatch({ + action: 'view_user', + member: member || {userId}, + }); + return success(); + }, + category: CommandCategories.advanced, + }), // Command definitions for autocompletion ONLY: // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2f19fc982cb..151d5a2b0d7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -197,12 +197,12 @@ "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", "Verified key": "Verified key", "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.", - "Displays action": "Displays action", "Forces the current outbound group session in an encrypted room to be discarded": "Forces the current outbound group session in an encrypted room to be discarded", "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", "Displays information about a user": "Displays information about a user", + "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", From 80479195c82e2c91da865962724846429b9461b2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 31 Mar 2020 11:53:26 +0100 Subject: [PATCH 4/4] iterate PR based on feedback. Remove newballsplease cmd alias Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 45 ++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 9fb0a151cf0..c62aa9c3e56 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -71,6 +71,16 @@ export const CommandCategories = { type RunFn = ((roomId: string, args: string, cmd: string) => {error: any} | {promise: Promise}); +interface ICommandOpts { + command: string; + aliases?: string[]; + args?: string; + description: string; + runFn?: RunFn; + category: string; + hideCompletionAfterSpace?: boolean; +} + class Command { command: string; aliases: string[]; @@ -80,30 +90,14 @@ class Command { category: string; hideCompletionAfterSpace: boolean; - constructor({ - command, - aliases = [], - args = '', - description, - runFn = undefined, - category = CommandCategories.other, - hideCompletionAfterSpace = false, - }: { - command: string; - aliases?: string[]; - args?: string; - description: string; - runFn?: RunFn; - category: string; - hideCompletionAfterSpace?: boolean; - }) { - this.command = command; - this.aliases = aliases; - this.args = args; - this.description = description; - this.runFn = runFn; - this.category = category; - this.hideCompletionAfterSpace = hideCompletionAfterSpace; + constructor(opts: ICommandOpts) { + this.command = opts.command; + this.aliases = opts.aliases || []; + this.args = opts.args || ""; + this.description = opts.description; + this.runFn = opts.runFn; + this.category = opts.category || CommandCategories.other; + this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false; } getCommand() { @@ -853,7 +847,6 @@ export const Commands = [ }), new Command({ command: 'discardsession', - aliases: ['newballsplease'], description: _td('Forces the current outbound group session in an encrypted room to be discarded'), runFn: function(roomId) { try { @@ -900,7 +893,7 @@ export const Commands = [ command: "whois", description: _td("Displays information about a user"), args: "", - runFn: function (roomId, userId) { + runFn: function(roomId, userId) { if (!userId || !userId.startsWith("@") || !userId.includes(":")) { return reject(this.getUsage()); }