2015-09-18 12:54:20 +00:00
/ *
2016-01-07 04:06:39 +00:00
Copyright 2015 , 2016 OpenMarket Ltd
2018-08-29 17:09:37 +00:00
Copyright 2018 New Vector Ltd
2019-05-09 21:12:21 +00:00
Copyright 2019 Michael Telatynski < 7t3chguy @ gmail.com >
2020-03-30 12:59:08 +00:00
Copyright 2020 The Matrix . org Foundation C . I . C .
2015-09-18 12:54:20 +00:00
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 .
* /
2020-03-30 12:59:08 +00:00
import * as React from "react" ;
2023-08-15 15:00:17 +00:00
import { User , IContent , Direction , ContentHelpers } from "matrix-js-sdk/src/matrix" ;
2021-12-09 09:10:23 +00:00
import { logger } from "matrix-js-sdk/src/logger" ;
2022-06-07 20:20:32 +00:00
import { MRoomTopicEventContent } from "matrix-js-sdk/src/@types/topic" ;
2021-12-09 09:10:23 +00:00
2020-05-14 02:41:41 +00:00
import dis from "./dispatcher/dispatcher" ;
2023-03-31 07:30:43 +00:00
import { _t , _td , UserFriendlyError } from "./languageHandler" ;
2016-09-13 10:11:52 +00:00
import Modal from "./Modal" ;
2018-11-29 22:05:53 +00:00
import MultiInviter from "./utils/MultiInviter" ;
2023-02-03 08:59:21 +00:00
import { Linkify , topicToHtml } from "./HtmlUtils" ;
2019-03-14 23:24:22 +00:00
import QuestionDialog from "./components/views/dialogs/QuestionDialog" ;
2019-03-24 06:07:00 +00:00
import WidgetUtils from "./utils/WidgetUtils" ;
2021-06-29 12:11:58 +00:00
import { textToHtmlRainbow } from "./utils/colour" ;
2022-01-28 10:02:37 +00:00
import { AddressType , getAddressType } from "./UserAddress" ;
2019-08-30 17:29:07 +00:00
import { abbreviateUrl } from "./utils/UrlUtils" ;
2022-07-29 11:01:15 +00:00
import { getDefaultIdentityServerUrl , setToDefaultIdentityServer } from "./utils/IdentityServerUtils" ;
2020-04-09 21:25:11 +00:00
import { WidgetType } from "./widgets/WidgetType" ;
2020-04-09 22:02:49 +00:00
import { Jitsi } from "./widgets/Jitsi" ;
2020-09-09 20:53:38 +00:00
import BugReportDialog from "./components/views/dialogs/BugReportDialog" ;
2020-05-11 09:54:28 +00:00
import { ensureDMExists } from "./createRoom" ;
2020-05-14 03:03:12 +00:00
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload" ;
import { Action } from "./dispatcher/actions" ;
2020-09-15 14:49:25 +00:00
import SdkConfig from "./SdkConfig" ;
2020-08-24 08:43:41 +00:00
import SettingsStore from "./settings/SettingsStore" ;
2021-10-08 22:04:26 +00:00
import { UIComponent , UIFeature } from "./settings/UIFeature" ;
2021-06-29 12:11:58 +00:00
import { CHAT_EFFECTS } from "./effects" ;
2022-08-30 19:13:39 +00:00
import LegacyCallHandler from "./LegacyCallHandler" ;
2021-06-29 12:11:58 +00:00
import { guessAndSetDMRoom } from "./Rooms" ;
2021-07-02 13:51:55 +00:00
import { upgradeRoom } from "./utils/RoomUpgrade" ;
2021-07-03 09:24:33 +00:00
import DevtoolsDialog from "./components/views/dialogs/DevtoolsDialog" ;
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog" ;
import InfoDialog from "./components/views/dialogs/InfoDialog" ;
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog" ;
2021-10-08 22:04:26 +00:00
import { shouldShowComponent } from "./customisations/helpers/UIComponents" ;
2021-11-18 12:47:11 +00:00
import { TimelineRenderingType } from "./contexts/RoomContext" ;
2022-02-10 14:29:55 +00:00
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload" ;
2022-02-25 15:58:13 +00:00
import VoipUserMapper from "./VoipUserMapper" ;
2022-06-07 20:20:32 +00:00
import { htmlSerializeFromMdIfNeeded } from "./editor/serialize" ;
2022-03-24 21:25:44 +00:00
import { leaveRoomBehaviour } from "./utils/leave-behaviour" ;
2023-06-14 12:49:18 +00:00
import { MatrixClientPeg } from "./MatrixClientPeg" ;
2023-06-28 12:39:34 +00:00
import { getDeviceCryptoInfo } from "./utils/crypto/deviceInfo" ;
2023-07-11 12:53:33 +00:00
import { isCurrentLocalRoom , reject , singleMxcUpload , success , successSync } from "./slash-commands/utils" ;
import { deop , op } from "./slash-commands/op" ;
import { CommandCategories } from "./slash-commands/interface" ;
import { Command } from "./slash-commands/command" ;
2023-07-14 11:20:59 +00:00
import { goto , join } from "./slash-commands/join" ;
2021-09-21 15:48:09 +00:00
2023-07-11 12:53:33 +00:00
export { CommandCategories , Command } ;
2017-05-23 08:44:11 +00:00
2020-03-30 12:59:08 +00:00
export const Commands = [
2021-02-25 21:59:27 +00:00
new Command ( {
command : "spoiler" ,
args : "<message>" ,
description : _td ( "Sends the given message as a spoiler" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , message = "" ) {
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeHtmlMessage ( message , ` <span data-mx-spoiler> ${ message } </span> ` ) ) ;
2021-02-25 21:59:27 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "shrug" ,
2019-02-18 23:27:22 +00:00
args : "<message>" ,
description : _td ( "Prepends ¯\\_(ツ)_/¯ to a plain-text message" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2019-02-20 05:46:17 +00:00
let message = "¯\\_(ツ)_/¯" ;
2019-02-18 23:27:22 +00:00
if ( args ) {
2019-02-20 05:46:17 +00:00
message = message + " " + args ;
2019-02-18 23:27:22 +00:00
}
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeTextMessage ( message ) ) ;
2019-02-20 05:46:17 +00:00
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.messages ,
2019-02-18 23:27:22 +00:00
} ) ,
2020-12-10 11:19:13 +00:00
new Command ( {
command : "tableflip" ,
args : "<message>" ,
description : _td ( "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2020-12-10 11:19:13 +00:00
let message = "(╯°□°)╯︵ ┻━┻" ;
if ( args ) {
message = message + " " + args ;
}
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeTextMessage ( message ) ) ;
2020-12-10 11:19:13 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-12-10 11:19:30 +00:00
new Command ( {
command : "unflip" ,
args : "<message>" ,
description : _td ( "Prepends ┬──┬ ノ ( ゜-゜ノ) to a plain-text message" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2020-12-10 11:19:30 +00:00
let message = "┬──┬ ノ ( ゜-゜ノ)" ;
if ( args ) {
message = message + " " + args ;
}
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeTextMessage ( message ) ) ;
2020-12-10 11:19:30 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-08-29 11:29:43 +00:00
new Command ( {
command : "lenny" ,
args : "<message>" ,
description : _td ( "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2020-08-29 11:29:43 +00:00
let message = "( ͡° ͜ʖ ͡°)" ;
if ( args ) {
message = message + " " + args ;
}
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeTextMessage ( message ) ) ;
2020-08-29 11:29:43 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "plain" ,
2019-09-02 15:44:31 +00:00
args : "<message>" ,
description : _td ( "Sends a message as plain text, without interpreting it as markdown" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , messages = "" ) {
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeTextMessage ( messages ) ) ;
2019-09-02 15:44:31 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-03-31 10:49:53 +00:00
new Command ( {
command : "html" ,
2020-03-29 19:45:06 +00:00
args : "<message>" ,
description : _td ( "Sends a message as html, without interpreting it as markdown" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , messages = "" ) {
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeHtmlMessage ( messages , messages ) ) ;
2020-03-29 19:45:06 +00:00
} ,
category : CommandCategories.messages ,
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "upgraderoom" ,
2019-01-17 22:59:05 +00:00
args : "<new_version>" ,
description : _td ( "Upgrades a room to a new version" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2019-01-17 22:59:05 +00:00
if ( args ) {
2019-08-28 11:00:37 +00:00
const room = cli . getRoom ( roomId ) ;
2023-02-16 17:21:44 +00:00
if ( ! room ? . currentState . mayClientSendStateEvent ( "m.room.tombstone" , cli ) ) {
2022-01-11 18:25:28 +00:00
return reject (
2023-03-31 07:30:43 +00:00
new UserFriendlyError ( "You do not have the required permissions to use this command." ) ,
2022-01-11 18:25:28 +00:00
) ;
2019-08-28 11:00:37 +00:00
}
2022-06-14 16:51:51 +00:00
const { finished } = Modal . createDialog (
2021-06-29 12:11:58 +00:00
RoomUpgradeWarningDialog ,
{ roomId : roomId , targetVersion : args } ,
2023-02-16 17:21:44 +00:00
/*className=*/ undefined ,
2019-12-03 00:26:08 +00:00
/*isPriority=*/ false ,
/*isStatic=*/ true ,
) ;
2019-11-28 03:29:11 +00:00
return success (
2023-01-12 13:25:14 +00:00
finished . then ( async ( [ resp ] ) : Promise < void > = > {
2021-07-02 13:51:55 +00:00
if ( ! resp ? . continue ) return ;
await upgradeRoom ( room , args , resp . invite ) ;
2019-08-28 11:00:37 +00:00
} ) ,
) ;
2019-01-17 22:59:05 +00:00
}
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-01-17 22:59:05 +00:00
} ) ,
2021-12-15 12:34:47 +00:00
new Command ( {
command : "jumptodate" ,
2022-04-14 15:13:18 +00:00
args : "<YYYY-MM-DD>" ,
description : _td ( "Jump to the given date in the timeline" ) ,
2021-12-15 12:34:47 +00:00
isEnabled : ( ) = > SettingsStore . getValue ( "feature_jump_to_date" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2021-12-15 12:34:47 +00:00
if ( args ) {
return success (
2023-01-12 13:25:14 +00:00
( async ( ) : Promise < void > = > {
2021-12-15 12:34:47 +00:00
const unixTimestamp = Date . parse ( args ) ;
if ( ! unixTimestamp ) {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError (
2023-08-22 15:32:05 +00:00
"We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD." ,
2023-03-31 07:30:43 +00:00
{ inputDate : args , cause : undefined } ,
2021-12-15 12:34:47 +00:00
) ;
}
const { event_id : eventId , origin_server_ts : originServerTs } = await cli . timestampToEvent (
roomId ,
unixTimestamp ,
Direction . Forward ,
) ;
logger . log (
` /timestamp_to_event: found ${ eventId } ( ${ originServerTs } ) for timestamp= ${ unixTimestamp } ` ,
) ;
2022-02-10 14:29:55 +00:00
dis . dispatch < ViewRoomPayload > ( {
2021-12-15 12:34:47 +00:00
action : Action.ViewRoom ,
2022-01-18 15:51:25 +00:00
event_id : eventId ,
2021-12-15 12:34:47 +00:00
highlighted : true ,
room_id : roomId ,
2022-02-17 18:03:27 +00:00
metricsTrigger : "SlashCommand" ,
metricsViaKeyboard : true ,
2021-12-15 12:34:47 +00:00
} ) ;
} ) ( ) ,
) ;
}
return reject ( this . getUsage ( ) ) ;
} ,
category : CommandCategories.actions ,
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "nick" ,
2018-06-18 18:31:40 +00:00
args : "<display_name>" ,
description : _td ( "Changes your display nickname" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
2023-05-25 15:29:48 +00:00
return success ( cli . setDisplayName ( args ) ) ;
2018-06-18 18:31:40 +00:00
}
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "myroomnick" ,
aliases : [ "roomnick" ] ,
2019-02-24 01:36:47 +00:00
args : "<display_name>" ,
description : _td ( "Changes your display nickname in the current room only" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2019-02-24 01:36:47 +00:00
if ( args ) {
2023-05-25 15:29:48 +00:00
const ev = cli . getRoom ( roomId ) ? . currentState . getStateEvents ( "m.room.member" , cli . getSafeUserId ( ) ) ;
2019-02-24 01:36:47 +00:00
const content = {
2021-09-21 15:48:09 +00:00
. . . ( ev ? ev . getContent ( ) : { membership : "join" } ) ,
2020-03-30 13:13:08 +00:00
displayname : args ,
2019-02-24 01:36:47 +00:00
} ;
2023-05-25 15:29:48 +00:00
return success ( cli . sendStateEvent ( roomId , "m.room.member" , content , cli . getSafeUserId ( ) ) ) ;
2019-02-24 01:36:47 +00:00
}
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-02-24 01:36:47 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "roomavatar" ,
2019-09-18 15:33:56 +00:00
args : "[<mxc_url>]" ,
description : _td ( "Changes the avatar of the current room" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2023-02-15 13:36:22 +00:00
let promise = Promise . resolve ( args ? ? null ) ;
2019-09-18 15:33:56 +00:00
if ( ! args ) {
2023-05-25 15:29:48 +00:00
promise = singleMxcUpload ( cli ) ;
2019-09-18 15:33:56 +00:00
}
return success (
promise . then ( ( url ) = > {
if ( ! url ) return ;
2023-05-25 15:29:48 +00:00
return cli . sendStateEvent ( roomId , "m.room.avatar" , { url } , "" ) ;
2019-09-18 15:33:56 +00:00
} ) ,
) ;
} ,
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-09-18 15:33:56 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "myroomavatar" ,
2019-05-10 19:28:28 +00:00
args : "[<mxc_url>]" ,
2023-07-07 10:54:43 +00:00
description : _td ( "Changes your profile picture in this current room only" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2019-05-09 21:12:21 +00:00
const room = cli . getRoom ( roomId ) ;
2023-05-25 15:29:48 +00:00
const userId = cli . getSafeUserId ( ) ;
2019-05-09 21:12:21 +00:00
2023-02-15 13:36:22 +00:00
let promise = Promise . resolve ( args ? ? null ) ;
2019-05-09 21:12:21 +00:00
if ( ! args ) {
2023-05-25 15:29:48 +00:00
promise = singleMxcUpload ( cli ) ;
2019-05-09 21:12:21 +00:00
}
return success (
promise . then ( ( url ) = > {
2019-06-29 06:05:43 +00:00
if ( ! url ) return ;
2023-02-15 13:36:22 +00:00
const ev = room ? . currentState . getStateEvents ( "m.room.member" , userId ) ;
2019-05-09 21:12:21 +00:00
const content = {
2021-09-21 15:48:09 +00:00
. . . ( ev ? ev . getContent ( ) : { membership : "join" } ) ,
2019-05-09 21:12:21 +00:00
avatar_url : url ,
} ;
return cli . sendStateEvent ( roomId , "m.room.member" , content , userId ) ;
} ) ,
) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-05-09 21:12:21 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "myavatar" ,
2019-06-27 18:38:12 +00:00
args : "[<mxc_url>]" ,
2023-07-07 10:54:43 +00:00
description : _td ( "Changes your profile picture in all rooms" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2023-02-15 13:36:22 +00:00
let promise = Promise . resolve ( args ? ? null ) ;
2019-06-27 18:38:12 +00:00
if ( ! args ) {
2023-05-25 15:29:48 +00:00
promise = singleMxcUpload ( cli ) ;
2019-06-27 18:38:12 +00:00
}
return success (
promise . then ( ( url ) = > {
2019-06-29 06:05:43 +00:00
if ( ! url ) return ;
2023-05-25 15:29:48 +00:00
return cli . setAvatarUrl ( url ) ;
2019-06-27 18:38:12 +00:00
} ) ,
) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-06-27 18:38:12 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "topic" ,
2019-01-30 10:22:05 +00:00
args : "[<topic>]" ,
description : _td ( "Gets or sets the room topic" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
2022-06-07 20:20:32 +00:00
const html = htmlSerializeFromMdIfNeeded ( args , { forceHTML : false } ) ;
return success ( cli . setRoomTopic ( roomId , args , html ) ) ;
2018-06-18 18:31:40 +00:00
}
2019-01-30 10:22:05 +00:00
const room = cli . getRoom ( roomId ) ;
2022-01-11 18:25:28 +00:00
if ( ! room ) {
return reject (
2023-03-31 07:30:43 +00:00
new UserFriendlyError ( "Failed to get room topic: Unable to find room (%(roomId)s" , {
roomId ,
cause : undefined ,
} ) ,
2022-01-11 18:25:28 +00:00
) ;
}
2019-01-30 10:22:05 +00:00
2023-02-15 13:36:22 +00:00
const content = room . currentState . getStateEvents ( "m.room.topic" , "" ) ? . getContent < MRoomTopicEventContent > ( ) ;
2022-06-07 20:20:32 +00:00
const topic = ! ! content
? ContentHelpers . parseTopicContent ( content )
: { text : _t ( "This room has no topic." ) } ;
2023-02-03 08:59:21 +00:00
const body = topicToHtml ( topic . text , topic . html , undefined , true ) ;
2019-01-30 10:22:05 +00:00
2022-06-14 16:51:51 +00:00
Modal . createDialog ( InfoDialog , {
2019-01-30 10:22:05 +00:00
title : room.name ,
2023-02-03 08:59:21 +00:00
description : < Linkify > { body } < / Linkify > ,
2020-05-12 09:51:27 +00:00
hasCloseButton : true ,
2022-06-07 20:20:32 +00:00
className : "markdown-body" ,
2019-01-30 10:22:05 +00:00
} ) ;
return success ( ) ;
2018-06-18 18:31:40 +00:00
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "roomname" ,
2019-01-03 23:42:17 +00:00
args : "<name>" ,
description : _td ( "Sets the room name" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2019-01-03 23:42:17 +00:00
if ( args ) {
2023-05-25 15:29:48 +00:00
return success ( cli . setRoomName ( roomId , args ) ) ;
2019-01-03 23:42:17 +00:00
}
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-01-03 23:42:17 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "invite" ,
2021-02-26 22:10:20 +00:00
args : "<user-id> [<reason>]" ,
2018-06-18 18:31:40 +00:00
description : _td ( "Invites user with given id to current room" ) ,
2022-02-10 13:11:10 +00:00
analyticsName : "Invite" ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) && shouldShowComponent ( UIComponent . InviteUsers ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
2021-02-26 22:10:20 +00:00
const [ address , reason ] = args . split ( /\s+(.+)/ ) ;
if ( address ) {
2018-11-29 22:05:53 +00:00
// We use a MultiInviter to re-use the invite logic, even though
// we're only inviting one user.
2019-08-30 17:29:07 +00:00
// If we need an identity server but don't have one, things
// get a bit more complex here, but we try to show something
// meaningful.
2020-07-12 23:19:15 +00:00
let prom = Promise . resolve ( ) ;
2023-05-25 15:29:48 +00:00
if ( getAddressType ( address ) === AddressType . Email && ! cli . getIdentityServerUrl ( ) ) {
2019-08-30 17:29:07 +00:00
const defaultIdentityServerUrl = getDefaultIdentityServerUrl ( ) ;
if ( defaultIdentityServerUrl ) {
2023-02-28 10:31:48 +00:00
const { finished } = Modal . createDialog ( QuestionDialog , {
2022-06-14 16:51:51 +00:00
title : _t ( "Use an identity server" ) ,
description : (
< p >
{ _t (
2023-08-22 15:32:05 +00:00
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings." ,
2022-06-14 16:51:51 +00:00
{
defaultIdentityServerName : abbreviateUrl ( defaultIdentityServerUrl ) ,
} ,
) }
< / p >
) ,
button : _t ( "Continue" ) ,
} ) ;
2020-04-01 15:53:25 +00:00
2020-07-12 23:19:15 +00:00
prom = finished . then ( ( [ useDefault ] ) = > {
2020-04-01 15:53:25 +00:00
if ( useDefault ) {
2023-05-25 15:29:48 +00:00
setToDefaultIdentityServer ( cli ) ;
2020-04-01 15:53:25 +00:00
return ;
}
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError (
2022-01-11 18:25:28 +00:00
"Use an identity server to invite by email. Manage in Settings." ,
) ;
2020-04-01 15:53:25 +00:00
} ) ;
2019-08-30 17:29:07 +00:00
} else {
2022-01-11 18:25:28 +00:00
return reject (
2023-03-31 07:30:43 +00:00
new UserFriendlyError ( "Use an identity server to invite by email. Manage in Settings." ) ,
2022-01-11 18:25:28 +00:00
) ;
2019-08-30 17:29:07 +00:00
}
}
2023-05-25 15:29:48 +00:00
const inviter = new MultiInviter ( cli , roomId ) ;
2020-07-12 23:19:15 +00:00
return success (
prom
. then ( ( ) = > {
2022-01-28 10:02:37 +00:00
return inviter . invite ( [ address ] , reason , true ) ;
2019-08-30 17:29:07 +00:00
} )
. then ( ( ) = > {
if ( inviter . getCompletionState ( address ) !== "invited" ) {
2023-03-31 07:30:43 +00:00
const errorStringFromInviterUtility = inviter . getErrorText ( address ) ;
if ( errorStringFromInviterUtility ) {
throw new Error ( errorStringFromInviterUtility ) ;
} else {
throw new UserFriendlyError (
"User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility" ,
{ user : address , roomId , cause : undefined } ,
) ;
}
2018-11-29 22:16:45 +00:00
}
2018-11-29 22:05:53 +00:00
} ) ,
) ;
2018-06-18 18:31:40 +00:00
}
2015-09-18 12:54:20 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2023-07-14 11:20:59 +00:00
goto ,
join ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "part" ,
2020-04-14 09:06:57 +00:00
args : "[<room-address>]" ,
2018-06-18 18:31:40 +00:00
description : _td ( "Leave room" ) ,
2022-02-10 13:11:10 +00:00
analyticsName : "Part" ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-11-07 13:45:34 +00:00
let targetRoomId : string | undefined ;
2018-06-18 18:31:40 +00:00
if ( args ) {
const matches = args . match ( /^(\S+)$/ ) ;
if ( matches ) {
let roomAlias = matches [ 1 ] ;
if ( roomAlias [ 0 ] !== "#" ) return reject ( this . getUsage ( ) ) ;
if ( ! roomAlias . includes ( ":" ) ) {
roomAlias += ":" + cli . getDomain ( ) ;
}
2015-09-18 12:54:20 +00:00
2018-06-18 18:31:40 +00:00
// Try to find a room with this alias
const rooms = cli . getRooms ( ) ;
2022-11-07 13:45:34 +00:00
targetRoomId = rooms . find ( ( room ) = > {
return room . getCanonicalAlias ( ) === roomAlias || room . getAltAliases ( ) . includes ( roomAlias ) ;
} ) ? . roomId ;
2022-01-11 18:25:28 +00:00
if ( ! targetRoomId ) {
2023-03-31 07:30:43 +00:00
return reject (
new UserFriendlyError ( "Unrecognised room address: %(roomAlias)s" , {
roomAlias ,
cause : undefined ,
} ) ,
) ;
2022-01-11 18:25:28 +00:00
}
2017-05-23 08:44:11 +00:00
}
2015-09-18 12:54:20 +00:00
}
2018-06-18 18:31:40 +00:00
if ( ! targetRoomId ) targetRoomId = roomId ;
2023-05-23 15:24:12 +00:00
return success ( leaveRoomBehaviour ( cli , targetRoomId ) ) ;
2018-06-18 18:31:40 +00:00
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
2022-01-14 13:08:34 +00:00
command : "remove" ,
aliases : [ "kick" ] ,
2018-06-18 18:31:40 +00:00
args : "<user-id> [reason]" ,
2022-01-14 13:08:34 +00:00
description : _td ( "Removes user with given id from this room" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
const matches = args . match ( /^(\S+?)( +(.*))?$/ ) ;
if ( matches ) {
2023-05-25 15:29:48 +00:00
return success ( cli . kick ( roomId , matches [ 1 ] , matches [ 3 ] ) ) ;
2018-06-18 18:31:40 +00:00
}
2015-09-18 12:54:20 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "ban" ,
2018-06-18 18:31:40 +00:00
args : "<user-id> [reason]" ,
description : _td ( "Bans user with given id" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
const matches = args . match ( /^(\S+?)( +(.*))?$/ ) ;
if ( matches ) {
2023-05-25 15:29:48 +00:00
return success ( cli . ban ( roomId , matches [ 1 ] , matches [ 3 ] ) ) ;
2018-06-18 18:31:40 +00:00
}
2015-09-18 12:54:20 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "unban" ,
2018-06-18 18:31:40 +00:00
args : "<user-id>" ,
2019-05-16 20:35:43 +00:00
description : _td ( "Unbans user with given ID" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
const matches = args . match ( /^(\S+)$/ ) ;
if ( matches ) {
// Reset the user membership to "leave" to unban him
2023-05-25 15:29:48 +00:00
return success ( cli . unban ( roomId , matches [ 1 ] ) ) ;
2018-06-18 18:31:40 +00:00
}
2015-09-18 12:54:20 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2016-01-14 14:39:58 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "ignore" ,
2018-06-18 18:31:40 +00:00
args : "<user-id>" ,
description : _td ( "Ignores a user, hiding their messages from you" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
2020-07-03 21:22:33 +00:00
const matches = args . match ( /^(@[^:]+:\S+)$/ ) ;
2018-06-18 18:31:40 +00:00
if ( matches ) {
const userId = matches [ 1 ] ;
const ignoredUsers = cli . getIgnoredUsers ( ) ;
ignoredUsers . push ( userId ) ; // de-duped internally in the js-sdk
return success (
cli . setIgnoredUsers ( ignoredUsers ) . then ( ( ) = > {
2022-06-14 16:51:51 +00:00
Modal . createDialog ( InfoDialog , {
2018-06-18 18:31:40 +00:00
title : _t ( "Ignored user" ) ,
description : (
< div >
2021-06-29 12:11:58 +00:00
< p > { _t ( "You are now ignoring %(userId)s" , { userId } ) } < / p >
2018-06-18 18:31:40 +00:00
< / div >
) ,
} ) ;
} ) ,
) ;
}
2017-09-14 22:08:51 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2017-09-14 22:08:51 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "unignore" ,
2018-06-18 18:31:40 +00:00
args : "<user-id>" ,
description : _td ( "Stops ignoring a user, showing their messages going forward" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
2020-07-03 21:22:33 +00:00
const matches = args . match ( /(^@[^:]+:\S+$)/ ) ;
2018-06-18 18:31:40 +00:00
if ( matches ) {
const userId = matches [ 1 ] ;
const ignoredUsers = cli . getIgnoredUsers ( ) ;
const index = ignoredUsers . indexOf ( userId ) ;
if ( index !== - 1 ) ignoredUsers . splice ( index , 1 ) ;
return success (
cli . setIgnoredUsers ( ignoredUsers ) . then ( ( ) = > {
2022-06-14 16:51:51 +00:00
Modal . createDialog ( InfoDialog , {
2018-06-18 18:31:40 +00:00
title : _t ( "Unignored user" ) ,
description : (
< div >
2021-06-29 12:11:58 +00:00
< p > { _t ( "You are no longer ignoring %(userId)s" , { userId } ) } < / p >
2018-06-18 18:31:40 +00:00
< / div >
) ,
} ) ;
} ) ,
) ;
}
2017-09-14 22:08:51 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.actions ,
2017-09-14 22:08:51 +00:00
} ) ,
2023-07-11 12:53:33 +00:00
op ,
deop ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "devtools" ,
2018-06-18 18:31:40 +00:00
description : _td ( "Opens the Developer Tools dialog" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadRootId ) {
Modal . createDialog ( DevtoolsDialog , { roomId , threadRootId } , "mx_DevtoolsDialog_wrapper" ) ;
2018-06-18 18:31:40 +00:00
return success ( ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.advanced ,
2017-07-31 11:08:28 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "addwidget" ,
2020-04-20 15:35:35 +00:00
args : "<url | embed code | Jitsi url>" ,
2019-03-24 06:07:00 +00:00
description : _td ( "Adds a custom widget by URL to the room" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = >
2022-08-16 13:20:26 +00:00
SettingsStore . getValue ( UIFeature . Widgets ) &&
shouldShowComponent ( UIComponent . AddIntegrations ) &&
2023-05-25 15:29:48 +00:00
! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , widgetUrl ) {
2020-04-09 22:02:49 +00:00
if ( ! widgetUrl ) {
2023-03-31 07:30:43 +00:00
return reject ( new UserFriendlyError ( "Please supply a widget URL or embed code" ) ) ;
2020-04-09 22:02:49 +00:00
}
// Try and parse out a widget URL from iframes
if ( widgetUrl . toLowerCase ( ) . startsWith ( "<iframe " ) ) {
2023-05-12 11:13:08 +00:00
const embed = new DOMParser ( ) . parseFromString ( widgetUrl , "text/html" ) . body ;
2023-03-07 13:19:18 +00:00
if ( embed ? . childNodes ? . length === 1 ) {
2023-05-12 11:13:08 +00:00
const iframe = embed . firstElementChild ;
2023-05-25 15:29:48 +00:00
if ( iframe ? . tagName . toLowerCase ( ) === "iframe" ) {
2021-09-21 15:48:09 +00:00
logger . log ( "Pulling URL out of iframe (embed code)" ) ;
2023-05-12 11:13:08 +00:00
if ( ! iframe . hasAttribute ( "src" ) ) {
return reject ( new UserFriendlyError ( "iframe has no src attribute" ) ) ;
}
2023-05-25 15:29:48 +00:00
widgetUrl = iframe . getAttribute ( "src" ) ! ;
2020-04-09 22:02:49 +00:00
}
}
}
if ( ! widgetUrl . startsWith ( "https://" ) && ! widgetUrl . startsWith ( "http://" ) ) {
2023-03-31 07:30:43 +00:00
return reject ( new UserFriendlyError ( "Please supply a https:// or http:// widget URL" ) ) ;
2019-03-24 06:07:00 +00:00
}
2023-05-25 15:29:48 +00:00
if ( WidgetUtils . canUserModifyWidgets ( cli , roomId ) ) {
const userId = cli . getUserId ( ) ;
2019-03-24 06:07:00 +00:00
const nowMs = new Date ( ) . getTime ( ) ;
const widgetId = encodeURIComponent ( ` ${ roomId } _ ${ userId } _ ${ nowMs } ` ) ;
2020-04-09 22:02:49 +00:00
let type = WidgetType . CUSTOM ;
2022-01-31 15:37:49 +00:00
let name = "Custom" ;
2020-04-09 22:02:49 +00:00
let data = { } ;
// Make the widget a Jitsi widget if it looks like a Jitsi widget
const jitsiData = Jitsi . getInstance ( ) . parsePreferredConferenceUrl ( widgetUrl ) ;
if ( jitsiData ) {
2021-09-21 15:48:09 +00:00
logger . log ( "Making /addwidget widget a Jitsi conference" ) ;
2020-04-09 22:02:49 +00:00
type = WidgetType . JITSI ;
2022-01-31 15:37:49 +00:00
name = "Jitsi" ;
2020-04-09 22:02:49 +00:00
data = jitsiData ;
widgetUrl = WidgetUtils . getLocalJitsiWrapperUrl ( ) ;
}
2023-05-25 15:29:48 +00:00
return success ( WidgetUtils . setRoomWidget ( cli , roomId , widgetId , type , widgetUrl , name , data ) ) ;
2019-03-24 06:07:00 +00:00
} else {
2023-03-31 07:30:43 +00:00
return reject ( new UserFriendlyError ( "You cannot modify widgets in this room." ) ) ;
2019-03-24 06:07:00 +00:00
}
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.admin ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2019-03-24 06:07:00 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "verify" ,
2018-06-18 18:31:40 +00:00
args : "<user-id> <device-id> <device-signing-key>" ,
2020-01-29 15:48:25 +00:00
description : _td ( "Verifies a user, session, and pubkey tuple" ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2018-06-18 18:31:40 +00:00
if ( args ) {
const matches = args . match ( /^(\S+) +(\S+) +(\S+)$/ ) ;
if ( matches ) {
const userId = matches [ 1 ] ;
const deviceId = matches [ 2 ] ;
const fingerprint = matches [ 3 ] ;
2017-05-23 08:24:18 +00:00
2020-01-03 12:08:35 +00:00
return success (
2023-01-12 13:25:14 +00:00
( async ( ) : Promise < void > = > {
2023-06-28 12:39:34 +00:00
const device = await getDeviceCryptoInfo ( cli , userId , deviceId ) ;
2020-01-03 12:08:35 +00:00
if ( ! device ) {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError (
"Unknown (user, session) pair: (%(userId)s, %(deviceId)s)" ,
{
userId ,
deviceId ,
cause : undefined ,
} ,
) ;
2020-01-03 12:08:35 +00:00
}
2023-04-24 13:19:46 +00:00
const deviceTrust = await cli . getCrypto ( ) ? . getDeviceVerificationStatus ( userId , deviceId ) ;
2022-12-12 11:24:14 +00:00
2023-04-24 13:19:46 +00:00
if ( deviceTrust ? . isVerified ( ) ) {
2020-01-03 12:08:35 +00:00
if ( device . getFingerprint ( ) === fingerprint ) {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Session already verified!" ) ;
2022-12-12 11:24:14 +00:00
} else {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError (
2023-02-13 11:31:22 +00:00
"WARNING: session already verified, but keys do NOT MATCH!" ,
2022-12-12 11:24:14 +00:00
) ;
}
}
2020-01-03 12:08:35 +00:00
if ( device . getFingerprint ( ) !== fingerprint ) {
const fprint = device . getFingerprint ( ) ;
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError (
2023-08-22 15:32:05 +00:00
'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!' ,
2020-01-03 12:08:35 +00:00
{
2022-12-12 11:24:14 +00:00
fprint ,
userId ,
2020-08-29 00:11:08 +00:00
deviceId ,
fingerprint ,
2023-03-31 07:30:43 +00:00
cause : undefined ,
2022-12-12 11:24:14 +00:00
} ,
) ;
}
2020-04-28 17:35:16 +00:00
await cli . setDeviceVerified ( userId , deviceId , true ) ;
2022-12-12 11:24:14 +00:00
2020-08-03 15:02:26 +00:00
// Tell the user we verified everything
2022-06-14 16:51:51 +00:00
Modal . createDialog ( InfoDialog , {
2020-01-03 12:08:35 +00:00
title : _t ( "Verified key" ) ,
2018-08-29 17:09:37 +00:00
description : (
2022-12-12 11:24:14 +00:00
< div >
< p >
{ _t (
2023-08-22 15:32:05 +00:00
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified." ,
2021-06-29 12:11:58 +00:00
{ userId , deviceId } ,
2020-01-03 12:08:35 +00:00
) }
< / p >
< / div >
) ,
} ) ;
} ) ( ) ,
) ;
2018-06-18 18:31:40 +00:00
}
2017-05-23 08:24:18 +00:00
}
2018-06-18 18:31:40 +00:00
return reject ( this . getUsage ( ) ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.advanced ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2018-06-18 18:31:40 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "discardsession" ,
2018-08-29 17:09:37 +00:00
description : _td ( "Forces the current outbound group session in an encrypted room to be discarded" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
runFn : function ( cli , roomId ) {
2018-08-29 17:09:37 +00:00
try {
2023-05-25 15:29:48 +00:00
cli . forceDiscardSession ( roomId ) ;
2018-08-29 17:09:37 +00:00
} catch ( e ) {
2023-07-05 10:53:22 +00:00
return reject ( e instanceof Error ? e.message : e ) ;
2018-08-29 17:09:37 +00:00
}
return success ( ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.advanced ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2018-08-29 17:09:37 +00:00
} ) ,
2022-07-11 19:18:50 +00:00
new Command ( {
command : "remakeolm" ,
description : _td ( "Developer command: Discards the current outbound group session and sets up new Olm sessions" ) ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > {
return SettingsStore . getValue ( "developerMode" ) && ! isCurrentLocalRoom ( cli ) ;
2022-07-11 19:18:50 +00:00
} ,
2023-05-25 15:29:48 +00:00
runFn : ( cli , roomId ) = > {
2022-07-11 19:18:50 +00:00
try {
2023-05-25 15:29:48 +00:00
const room = cli . getRoom ( roomId ) ;
2022-07-11 19:18:50 +00:00
2023-05-25 15:29:48 +00:00
cli . forceDiscardSession ( roomId ) ;
2022-07-11 19:18:50 +00:00
2022-10-12 17:28:30 +00:00
return success (
2023-02-15 13:36:22 +00:00
room ? . getEncryptionTargetMembers ( ) . then ( ( members ) = > {
2022-10-12 17:28:30 +00:00
// noinspection JSIgnoredPromiseFromCall
2023-05-25 15:29:48 +00:00
cli . crypto ? . ensureOlmSessionsForUsers (
2022-10-12 17:28:30 +00:00
members . map ( ( m ) = > m . userId ) ,
true ,
) ;
} ) ,
) ;
2022-07-11 19:18:50 +00:00
} catch ( e ) {
2023-07-05 10:53:22 +00:00
return reject ( e instanceof Error ? e.message : e ) ;
2022-07-11 19:18:50 +00:00
}
} ,
category : CommandCategories.advanced ,
renderingTypes : [ TimelineRenderingType . Room ] ,
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "rainbow" ,
2019-05-12 15:40:27 +00:00
description : _td ( "Sends the given message coloured as a rainbow" ) ,
2019-05-12 15:36:43 +00:00
args : "<message>" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-11-21 11:24:59 +00:00
if ( ! args ) return reject ( this . getUsage ( ) ) ;
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeHtmlMessage ( args , textToHtmlRainbow ( args ) ) ) ;
2019-05-12 15:36:43 +00:00
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.messages ,
2019-05-12 15:36:43 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "rainbowme" ,
2019-05-12 15:40:27 +00:00
description : _td ( "Sends the given emote coloured as a rainbow" ) ,
2019-05-12 15:36:43 +00:00
args : "<message>" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-11-21 11:24:59 +00:00
if ( ! args ) return reject ( this . getUsage ( ) ) ;
2021-06-17 10:37:06 +00:00
return successSync ( ContentHelpers . makeHtmlEmote ( args , textToHtmlRainbow ( args ) ) ) ;
2019-05-12 15:36:43 +00:00
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.messages ,
2019-05-12 16:14:30 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
new Command ( {
command : "help" ,
2019-07-29 16:58:34 +00:00
description : _td ( "Displays list of commands with usages and descriptions" ) ,
runFn : function ( ) {
2022-06-14 16:51:51 +00:00
Modal . createDialog ( SlashCommandHelpDialog ) ;
2019-07-29 16:58:34 +00:00
return success ( ) ;
} ,
2019-08-06 17:03:38 +00:00
category : CommandCategories.advanced ,
2019-07-29 16:58:34 +00:00
} ) ,
2020-03-30 13:13:08 +00:00
new Command ( {
command : "whois" ,
description : _td ( "Displays information about a user" ) ,
args : "<user-id>" ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , userId ) {
2020-03-30 13:13:08 +00:00
if ( ! userId || ! userId . startsWith ( "@" ) || ! userId . includes ( ":" ) ) {
return reject ( this . getUsage ( ) ) ;
}
2023-05-25 15:29:48 +00:00
const member = cli . getRoom ( roomId ) ? . getMember ( userId ) ;
2020-05-14 03:03:12 +00:00
dis . dispatch < ViewUserPayload > ( {
action : Action.ViewUser ,
2021-06-18 15:21:46 +00:00
// XXX: We should be using a real member object and not assuming what the receiver wants.
member : member || ( { userId } as User ) ,
2020-03-30 13:13:08 +00:00
} ) ;
return success ( ) ;
} ,
category : CommandCategories.advanced ,
} ) ,
2020-04-19 11:09:07 +00:00
new Command ( {
command : "rageshake" ,
aliases : [ "bugreport" ] ,
description : _td ( "Send a bug report with logs" ) ,
2020-09-15 14:49:25 +00:00
isEnabled : ( ) = > ! ! SdkConfig . get ( ) . bug_report_endpoint_url ,
2020-04-19 11:09:07 +00:00
args : "<description>" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2020-04-19 11:19:31 +00:00
return success (
2022-06-14 16:51:51 +00:00
Modal . createDialog ( BugReportDialog , {
2020-09-09 20:53:38 +00:00
initialText : args ,
} ) . finished ,
2020-04-19 11:19:31 +00:00
) ;
2020-04-19 11:09:07 +00:00
} ,
category : CommandCategories.advanced ,
} ) ,
2022-02-25 15:58:13 +00:00
new Command ( {
command : "tovirtual" ,
description : _td ( "Switches to this room's virtual room, if it has one" ) ,
category : CommandCategories.advanced ,
2023-05-25 15:29:48 +00:00
isEnabled ( cli ) : boolean {
return ! ! LegacyCallHandler . instance . getSupportsVirtualRooms ( ) && ! isCurrentLocalRoom ( cli ) ;
2022-02-25 15:58:13 +00:00
} ,
2023-05-25 15:29:48 +00:00
runFn : ( cli , roomId ) = > {
2022-02-25 15:58:13 +00:00
return success (
2023-01-12 13:25:14 +00:00
( async ( ) : Promise < void > = > {
2022-02-25 15:58:13 +00:00
const room = await VoipUserMapper . sharedInstance ( ) . getVirtualRoomForRoom ( roomId ) ;
2023-03-31 07:30:43 +00:00
if ( ! room ) throw new UserFriendlyError ( "No virtual room for this room" ) ;
2022-02-25 15:58:13 +00:00
dis . dispatch < ViewRoomPayload > ( {
action : Action.ViewRoom ,
room_id : room.roomId ,
metricsTrigger : "SlashCommand" ,
metricsViaKeyboard : true ,
} ) ;
} ) ( ) ,
) ;
} ,
} ) ,
2020-05-11 09:54:28 +00:00
new Command ( {
command : "query" ,
description : _td ( "Opens chat with the given user" ) ,
args : "<user-id>" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , userId ) {
2020-11-04 15:32:21 +00:00
// easter-egg for now: look up phone numbers through the thirdparty API
// (very dumb phone number detection...)
const isPhoneNumber = userId && /^\+?[0123456789]+$/ . test ( userId ) ;
if ( ! userId || ( ( ! userId . startsWith ( "@" ) || ! userId . includes ( ":" ) ) && ! isPhoneNumber ) ) {
2020-05-11 09:54:28 +00:00
return reject ( this . getUsage ( ) ) ;
}
return success (
2023-01-12 13:25:14 +00:00
( async ( ) : Promise < void > = > {
2020-11-04 15:32:21 +00:00
if ( isPhoneNumber ) {
2022-11-21 11:24:59 +00:00
const results = await LegacyCallHandler . instance . pstnLookup ( userId ) ;
2020-11-04 15:32:21 +00:00
if ( ! results || results . length === 0 || ! results [ 0 ] . userid ) {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Unable to find Matrix ID for phone number" ) ;
2022-12-12 11:24:14 +00:00
}
2020-11-04 15:32:21 +00:00
userId = results [ 0 ] . userid ;
}
2023-05-25 15:29:48 +00:00
const roomId = await ensureDMExists ( cli , userId ) ;
2023-03-07 13:19:18 +00:00
if ( ! roomId ) throw new Error ( "Failed to ensure DM exists" ) ;
2020-11-04 15:32:21 +00:00
2022-02-10 14:29:55 +00:00
dis . dispatch < ViewRoomPayload > ( {
2021-11-25 20:49:43 +00:00
action : Action.ViewRoom ,
2020-11-04 15:32:21 +00:00
room_id : roomId ,
2022-02-17 18:03:27 +00:00
metricsTrigger : "SlashCommand" ,
metricsViaKeyboard : true ,
2020-05-11 09:54:28 +00:00
} ) ;
} ) ( ) ,
) ;
} ,
category : CommandCategories.actions ,
} ) ,
new Command ( {
command : "msg" ,
description : _td ( "Sends a message to the given user" ) ,
2021-10-25 10:40:33 +00:00
args : "<user-id> [<message>]" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2020-05-11 09:54:28 +00:00
if ( args ) {
2020-05-12 09:23:53 +00:00
// matches the first whitespace delimited group and then the rest of the string
2020-05-11 09:54:28 +00:00
const matches = args . match ( / ^ ( \ S + ? ) ( ? : + ( . * ) ) ? $ / s ) ;
if ( matches ) {
const [ userId , msg ] = matches . slice ( 1 ) ;
2021-10-25 10:40:33 +00:00
if ( userId && userId . startsWith ( "@" ) && userId . includes ( ":" ) ) {
2020-05-11 09:54:28 +00:00
return success (
2023-01-12 13:25:14 +00:00
( async ( ) : Promise < void > = > {
2020-05-11 09:54:28 +00:00
const roomId = await ensureDMExists ( cli , userId ) ;
2023-03-07 13:19:18 +00:00
if ( ! roomId ) throw new Error ( "Failed to ensure DM exists" ) ;
2022-02-10 14:29:55 +00:00
dis . dispatch < ViewRoomPayload > ( {
2021-11-25 20:49:43 +00:00
action : Action.ViewRoom ,
2020-05-11 09:54:28 +00:00
room_id : roomId ,
2022-02-17 18:03:27 +00:00
metricsTrigger : "SlashCommand" ,
metricsViaKeyboard : true ,
2020-05-11 09:54:28 +00:00
} ) ;
2021-10-25 10:40:33 +00:00
if ( msg ) {
cli . sendTextMessage ( roomId , msg ) ;
}
2020-05-11 09:54:28 +00:00
} ) ( ) ,
) ;
}
}
}
return reject ( this . getUsage ( ) ) ;
} ,
category : CommandCategories.actions ,
} ) ,
2020-10-29 17:56:24 +00:00
new Command ( {
command : "holdcall" ,
2020-11-05 09:49:14 +00:00
description : _td ( "Places the call in the current room on hold" ) ,
2020-10-29 17:56:24 +00:00
category : CommandCategories.other ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-08-30 19:13:39 +00:00
const call = LegacyCallHandler . instance . getCallForRoom ( roomId ) ;
2020-10-29 17:56:24 +00:00
if ( ! call ) {
2023-03-31 07:30:43 +00:00
return reject ( new UserFriendlyError ( "No active call in this room" ) ) ;
2020-10-29 17:56:24 +00:00
}
call . setRemoteOnHold ( true ) ;
return success ( ) ;
} ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2020-10-29 17:56:24 +00:00
} ) ,
new Command ( {
command : "unholdcall" ,
2020-11-05 09:49:14 +00:00
description : _td ( "Takes the call in the current room off hold" ) ,
2020-10-29 17:56:24 +00:00
category : CommandCategories.other ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-08-30 19:13:39 +00:00
const call = LegacyCallHandler . instance . getCallForRoom ( roomId ) ;
2020-10-29 17:56:24 +00:00
if ( ! call ) {
2023-03-31 07:30:43 +00:00
return reject ( new UserFriendlyError ( "No active call in this room" ) ) ;
2020-10-29 17:56:24 +00:00
}
call . setRemoteOnHold ( false ) ;
return success ( ) ;
} ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2020-10-29 17:56:24 +00:00
} ) ,
2021-01-13 14:30:09 +00:00
new Command ( {
command : "converttodm" ,
description : _td ( "Converts the room to a DM" ) ,
category : CommandCategories.other ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2023-05-25 15:29:48 +00:00
const room = cli . getRoom ( roomId ) ;
2023-03-31 07:30:43 +00:00
if ( ! room ) return reject ( new UserFriendlyError ( "Could not find room" ) ) ;
2021-01-13 14:30:09 +00:00
return success ( guessAndSetDMRoom ( room , true ) ) ;
} ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2021-01-13 14:30:09 +00:00
} ) ,
new Command ( {
command : "converttoroom" ,
description : _td ( "Converts the DM to a room" ) ,
category : CommandCategories.other ,
2023-05-25 15:29:48 +00:00
isEnabled : ( cli ) = > ! isCurrentLocalRoom ( cli ) ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2023-05-25 15:29:48 +00:00
const room = cli . getRoom ( roomId ) ;
2023-03-31 07:30:43 +00:00
if ( ! room ) return reject ( new UserFriendlyError ( "Could not find room" ) ) ;
2021-01-13 14:30:09 +00:00
return success ( guessAndSetDMRoom ( room , false ) ) ;
} ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2021-01-13 14:30:09 +00:00
} ) ,
2020-02-29 01:29:52 +00:00
2020-03-30 12:59:08 +00:00
// 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 ( {
2020-04-10 12:59:01 +00:00
command : "me" ,
2020-03-30 12:59:08 +00:00
args : "<message>" ,
description : _td ( "Displays action" ) ,
category : CommandCategories.messages ,
hideCompletionAfterSpace : true ,
2020-02-29 01:29:52 +00:00
} ) ,
2020-11-26 17:27:35 +00:00
2020-11-27 13:54:21 +00:00
. . . CHAT_EFFECTS . map ( ( effect ) = > {
2020-10-21 11:37:36 +00:00
return new Command ( {
command : effect.command ,
description : effect.description ( ) ,
args : "<message>" ,
2023-07-07 14:40:25 +00:00
runFn : function ( cli , roomId , threadId , args ) {
2022-07-27 10:03:25 +00:00
let content : IContent ;
if ( ! args ) {
content = ContentHelpers . makeEmoteMessage ( effect . fallbackMessage ( ) ) ;
} else {
content = {
msgtype : effect.msgType ,
body : args ,
} ;
}
dis . dispatch ( { action : ` effects. ${ effect . command } ` } ) ;
return successSync ( content ) ;
2020-10-21 11:37:36 +00:00
} ,
category : CommandCategories.effects ,
2021-11-18 12:47:11 +00:00
renderingTypes : [ TimelineRenderingType . Room ] ,
2021-06-29 12:11:58 +00:00
} ) ;
2020-08-18 15:57:51 +00:00
} ) ,
2020-03-30 12:59:08 +00:00
] ;
// build a map from names and aliases to the Command objects.
2021-06-30 12:01:26 +00:00
export const CommandMap = new Map < string , Command > ( ) ;
2020-03-30 12:59:08 +00:00
Commands . forEach ( ( cmd ) = > {
CommandMap . set ( cmd . command , cmd ) ;
cmd . aliases . forEach ( ( alias ) = > {
CommandMap . set ( alias , cmd ) ;
} ) ;
} ) ;
2015-09-18 12:54:20 +00:00
2021-06-30 12:01:26 +00:00
export function parseCommandString ( input : string ) : { cmd? : string ; args? : string } {
2023-04-17 12:57:19 +00:00
// trim any trailing whitespace, as it can confuse the parser for IRC-style commands
input = input . trimEnd ( ) ;
2020-07-18 10:40:45 +00:00
if ( input [ 0 ] !== "/" ) return { } ; // not a command
2018-06-18 18:31:40 +00:00
2021-02-15 12:16:32 +00:00
const bits = input . match ( /^(\S+?)(?:[ \n]+((.|\n)*))?$/ ) ;
2021-06-30 12:01:26 +00:00
let cmd : string ;
2023-02-16 17:21:44 +00:00
let args : string | undefined ;
2018-06-18 18:31:40 +00:00
if ( bits ) {
cmd = bits [ 1 ] . substring ( 1 ) . toLowerCase ( ) ;
2020-01-21 16:50:04 +00:00
args = bits [ 2 ] ;
2018-06-18 18:31:40 +00:00
} else {
cmd = input ;
}
2021-06-29 12:11:58 +00:00
return { cmd , args } ;
2020-04-10 12:59:01 +00:00
}
2021-06-30 12:01:26 +00:00
interface ICmd {
cmd? : Command ;
args? : string ;
}
2020-04-10 12:59:01 +00:00
/ * *
2022-06-07 20:20:32 +00:00
* Process the given text for / c o m m a n d s a n d r e t u r n s a p a r s e d c o m m a n d t h a t c a n b e u s e d f o r r u n n i n g t h e o p e r a t i o n .
2020-04-10 12:59:01 +00:00
* @param { string } input The raw text input by the user .
2022-06-07 20:20:32 +00:00
* @return { ICmd } The parsed command object .
* Returns an empty object if the input didn ' t match a command .
2020-04-10 12:59:01 +00:00
* /
2021-06-30 12:01:26 +00:00
export function getCommand ( input : string ) : ICmd {
2021-06-29 12:11:58 +00:00
const { cmd , args } = parseCommandString ( input ) ;
2020-04-10 12:59:01 +00:00
2023-06-14 12:49:18 +00:00
if ( cmd && CommandMap . has ( cmd ) && CommandMap . get ( cmd ) ! . isEnabled ( MatrixClientPeg . get ( ) ) ) {
2021-02-25 19:39:20 +00:00
return {
cmd : CommandMap.get ( cmd ) ,
args ,
} ;
2018-06-18 18:31:40 +00:00
}
2021-04-13 09:33:32 +00:00
return { } ;
2018-06-18 18:31:40 +00:00
}