matrix-react-sdk
Version:
SDK for matrix.org using React
1,310 lines (1,090 loc) • 166 kB
JavaScript
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseCommandString = parseCommandString;
exports.getCommand = getCommand;
exports.CommandMap = exports.Commands = exports.Command = exports.CommandCategories = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var React = _interopRequireWildcard(require("react"));
var ContentHelpers = _interopRequireWildcard(require("matrix-js-sdk/src/content-helpers"));
var _MatrixClientPeg = require("./MatrixClientPeg");
var _dispatcher = _interopRequireDefault(require("./dispatcher/dispatcher"));
var sdk = _interopRequireWildcard(require("./index"));
var _languageHandler = require("./languageHandler");
var _Modal = _interopRequireDefault(require("./Modal"));
var _MultiInviter = _interopRequireDefault(require("./utils/MultiInviter"));
var _HtmlUtils = require("./HtmlUtils");
var _QuestionDialog = _interopRequireDefault(require("./components/views/dialogs/QuestionDialog"));
var _WidgetUtils = _interopRequireDefault(require("./utils/WidgetUtils"));
var _colour = require("./utils/colour");
var _UserAddress = require("./UserAddress");
var _UrlUtils = require("./utils/UrlUtils");
var _IdentityServerUtils = require("./utils/IdentityServerUtils");
var _Permalinks = require("./utils/permalinks/Permalinks");
var _RoomInvite = require("./RoomInvite");
var _WidgetType = require("./widgets/WidgetType");
var _Jitsi = require("./widgets/Jitsi");
var _parse = require("parse5");
var _BugReportDialog = _interopRequireDefault(require("./components/views/dialogs/BugReportDialog"));
var _createRoom = require("./createRoom");
var _actions = require("./dispatcher/actions");
var _membership = require("./utils/membership");
var _SdkConfig = _interopRequireDefault(require("./SdkConfig"));
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _UIFeature = require("./settings/UIFeature");
var _effects = require("./effects");
var _CallHandler = _interopRequireDefault(require("./CallHandler"));
var _Rooms = require("./Rooms");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
const singleMxcUpload = async () =>
/*: Promise<any>*/
{
return new Promise(resolve => {
const fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');
fileSelector.onchange = (ev
/*: HTMLInputEvent*/
) => {
const file = ev.target.files[0];
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
_Modal.default.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
file,
onFinished: shouldContinue => {
resolve(shouldContinue ? _MatrixClientPeg.MatrixClientPeg.get().uploadContent(file) : null);
}
});
};
fileSelector.click();
});
};
const CommandCategories = {
"messages": (0, _languageHandler._td)("Messages"),
"actions": (0, _languageHandler._td)("Actions"),
"admin": (0, _languageHandler._td)("Admin"),
"advanced": (0, _languageHandler._td)("Advanced"),
"effects": (0, _languageHandler._td)("Effects"),
"other": (0, _languageHandler._td)("Other")
};
exports.CommandCategories = CommandCategories;
class Command {
constructor(opts
/*: ICommandOpts*/
) {
(0, _defineProperty2.default)(this, "command", void 0);
(0, _defineProperty2.default)(this, "aliases", void 0);
(0, _defineProperty2.default)(this, "args", void 0);
(0, _defineProperty2.default)(this, "description", void 0);
(0, _defineProperty2.default)(this, "runFn", void 0);
(0, _defineProperty2.default)(this, "category", void 0);
(0, _defineProperty2.default)(this, "hideCompletionAfterSpace", void 0);
(0, _defineProperty2.default)(this, "_isEnabled", void 0);
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;
this._isEnabled = opts.isEnabled;
}
getCommand() {
return `/${this.command}`;
}
getCommandWithArgs() {
return this.getCommand() + " " + this.args;
}
run(roomId
/*: string*/
, args
/*: string*/
) {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) return reject((0, _languageHandler._t)("Command error"));
return this.runFn.bind(this)(roomId, args);
}
getUsage() {
return (0, _languageHandler._t)('Usage') + ': ' + this.getCommandWithArgs();
}
isEnabled() {
return this._isEnabled ? this._isEnabled() : true;
}
}
exports.Command = Command;
function reject(error) {
return {
error
};
}
function success(promise
/*: Promise<any>*/
) {
return {
promise
};
}
/* Disable the "unexpected this" error for these commands - all of the run
* functions are called with `this` bound to the Command instance.
*/
const Commands = [new Command({
command: 'spoiler',
args: '<message>',
description: (0, _languageHandler._td)('Sends the given message as a spoiler'),
runFn: function (roomId, message) {
return success(ContentHelpers.makeHtmlMessage(message, `<span data-mx-spoiler>${message}</span>`));
},
category: CommandCategories.messages
}), new Command({
command: 'shrug',
args: '<message>',
description: (0, _languageHandler._td)('Prepends ¯\\_(ツ)_/¯ to a plain-text message'),
runFn: function (roomId, args) {
let message = '¯\\_(ツ)_/¯';
if (args) {
message = message + ' ' + args;
}
return success(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages
}), new Command({
command: 'tableflip',
args: '<message>',
description: (0, _languageHandler._td)('Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message'),
runFn: function (roomId, args) {
let message = '(╯°□°)╯︵ ┻━┻';
if (args) {
message = message + ' ' + args;
}
return success(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages
}), new Command({
command: 'unflip',
args: '<message>',
description: (0, _languageHandler._td)('Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message'),
runFn: function (roomId, args) {
let message = '┬──┬ ノ( ゜-゜ノ)';
if (args) {
message = message + ' ' + args;
}
return success(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages
}), new Command({
command: 'lenny',
args: '<message>',
description: (0, _languageHandler._td)('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'),
runFn: function (roomId, args) {
let message = '( ͡° ͜ʖ ͡°)';
if (args) {
message = message + ' ' + args;
}
return success(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages
}), new Command({
command: 'plain',
args: '<message>',
description: (0, _languageHandler._td)('Sends a message as plain text, without interpreting it as markdown'),
runFn: function (roomId, messages) {
return success(ContentHelpers.makeTextMessage(messages));
},
category: CommandCategories.messages
}), new Command({
command: 'html',
args: '<message>',
description: (0, _languageHandler._td)('Sends a message as html, without interpreting it as markdown'),
runFn: function (roomId, messages) {
return success(ContentHelpers.makeHtmlMessage(messages, messages));
},
category: CommandCategories.messages
}), new Command({
command: 'ddg',
args: '<query>',
description: (0, _languageHandler._td)('Searches DuckDuckGo for results'),
runFn: function () {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); // TODO Don't explain this away, actually show a search UI here.
_Modal.default.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
title: (0, _languageHandler._t)('/ddg is not a command'),
description: (0, _languageHandler._t)('To use it, just wait for autocomplete results to load and tab through them.')
});
return success();
},
category: CommandCategories.actions,
hideCompletionAfterSpace: true
}), new Command({
command: 'upgraderoom',
args: '<new_version>',
description: (0, _languageHandler._td)('Upgrades a room to a new version'),
runFn: function (roomId, args) {
if (args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject((0, _languageHandler._t)("You do not have the required permissions to use this command."));
}
const RoomUpgradeWarningDialog = sdk.getComponent("dialogs.RoomUpgradeWarningDialog");
const {
finished
} = _Modal.default.createTrackedDialog('Slash Commands', 'upgrade room confirmation', RoomUpgradeWarningDialog, {
roomId: roomId,
targetVersion: args
},
/*className=*/
null,
/*isPriority=*/
false,
/*isStatic=*/
true);
return success(finished.then(async ([resp]) => {
if (!resp.continue) return;
let checkForUpgradeFn;
try {
const upgradePromise = cli.upgradeRoom(roomId, args); // We have to wait for the js-sdk to give us the room back so
// we can more effectively abuse the MultiInviter behaviour
// which heavily relies on the Room object being available.
if (resp.invite) {
checkForUpgradeFn = async newRoom => {
// The upgradePromise should be done by the time we await it here.
const {
replacement_room: newRoomId
} = await upgradePromise;
if (newRoom.roomId !== newRoomId) return;
const toInvite = [...room.getMembersWithMembership("join"), ...room.getMembersWithMembership("invite")].map(m => m.userId).filter(m => m !== cli.getUserId());
if (toInvite.length > 0) {
// Errors are handled internally to this function
await (0, _RoomInvite.inviteUsersToRoom)(newRoomId, toInvite);
}
cli.removeListener('Room', checkForUpgradeFn);
};
cli.on('Room', checkForUpgradeFn);
} // We have to await after so that the checkForUpgradesFn has a proper reference
// to the new room's ID.
await upgradePromise;
} catch (e) {
console.error(e);
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
title: (0, _languageHandler._t)('Error upgrading room'),
description: (0, _languageHandler._t)('Double check that your server supports the room version chosen and try again.')
});
}
}));
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'nick',
args: '<display_name>',
description: (0, _languageHandler._td)('Changes your display nickname'),
runFn: function (roomId, args) {
if (args) {
return success(_MatrixClientPeg.MatrixClientPeg.get().setDisplayName(args));
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'myroomnick',
aliases: ['roomnick'],
args: '<display_name>',
description: (0, _languageHandler._td)('Changes your display nickname in the current room only'),
runFn: function (roomId, args) {
if (args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const ev = cli.getRoom(roomId).currentState.getStateEvents('m.room.member', cli.getUserId());
const content = _objectSpread(_objectSpread({}, ev ? ev.getContent() : {
membership: 'join'
}), {}, {
displayname: args
});
return success(cli.sendStateEvent(roomId, 'm.room.member', content, cli.getUserId()));
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'roomavatar',
args: '[<mxc_url>]',
description: (0, _languageHandler._td)('Changes the avatar of the current room'),
runFn: function (roomId, args) {
let promise = Promise.resolve(args);
if (!args) {
promise = singleMxcUpload();
}
return success(promise.then(url => {
if (!url) return;
return _MatrixClientPeg.MatrixClientPeg.get().sendStateEvent(roomId, 'm.room.avatar', {
url
}, '');
}));
},
category: CommandCategories.actions
}), new Command({
command: 'myroomavatar',
args: '[<mxc_url>]',
description: (0, _languageHandler._td)('Changes your avatar in this current room only'),
runFn: function (roomId, args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const room = cli.getRoom(roomId);
const userId = cli.getUserId();
let promise = Promise.resolve(args);
if (!args) {
promise = singleMxcUpload();
}
return success(promise.then(url => {
if (!url) return;
const ev = room.currentState.getStateEvents('m.room.member', userId);
const content = _objectSpread(_objectSpread({}, ev ? ev.getContent() : {
membership: 'join'
}), {}, {
avatar_url: url
});
return cli.sendStateEvent(roomId, 'm.room.member', content, userId);
}));
},
category: CommandCategories.actions
}), new Command({
command: 'myavatar',
args: '[<mxc_url>]',
description: (0, _languageHandler._td)('Changes your avatar in all rooms'),
runFn: function (roomId, args) {
let promise = Promise.resolve(args);
if (!args) {
promise = singleMxcUpload();
}
return success(promise.then(url => {
if (!url) return;
return _MatrixClientPeg.MatrixClientPeg.get().setAvatarUrl(url);
}));
},
category: CommandCategories.actions
}), new Command({
command: 'topic',
args: '[<topic>]',
description: (0, _languageHandler._td)('Gets or sets the room topic'),
runFn: function (roomId, args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
if (args) {
return success(cli.setRoomTopic(roomId, args));
}
const room = cli.getRoom(roomId);
if (!room) return reject((0, _languageHandler._t)("Failed to set topic"));
const topicEvents = room.currentState.getStateEvents('m.room.topic', '');
const topic = topicEvents && topicEvents.getContent().topic;
const topicHtml = topic ? (0, _HtmlUtils.linkifyAndSanitizeHtml)(topic) : (0, _languageHandler._t)('This room has no topic.');
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
title: room.name,
description: /*#__PURE__*/React.createElement("div", {
dangerouslySetInnerHTML: {
__html: topicHtml
}
}),
hasCloseButton: true
});
return success();
},
category: CommandCategories.admin
}), new Command({
command: 'roomname',
args: '<name>',
description: (0, _languageHandler._td)('Sets the room name'),
runFn: function (roomId, args) {
if (args) {
return success(_MatrixClientPeg.MatrixClientPeg.get().setRoomName(roomId, args));
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'invite',
args: '<user-id> [<reason>]',
description: (0, _languageHandler._td)('Invites user with given id to current room'),
runFn: function (roomId, args) {
if (args) {
const [address, reason] = args.split(/\s+(.+)/);
if (address) {
// We use a MultiInviter to re-use the invite logic, even though
// we're only inviting one user.
// 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.
let prom = Promise.resolve();
if ((0, _UserAddress.getAddressType)(address) === 'email' && !_MatrixClientPeg.MatrixClientPeg.get().getIdentityServerUrl()) {
const defaultIdentityServerUrl = (0, _IdentityServerUtils.getDefaultIdentityServerUrl)();
if (defaultIdentityServerUrl) {
const {
finished
} = _Modal.default.createTrackedDialog('Slash Commands', 'Identity server', _QuestionDialog.default, {
title: (0, _languageHandler._t)("Use an identity server"),
description: /*#__PURE__*/React.createElement("p", null, (0, _languageHandler._t)("Use an identity server to invite by email. " + "Click continue to use the default identity server " + "(%(defaultIdentityServerName)s) or manage in Settings.", {
defaultIdentityServerName: (0, _UrlUtils.abbreviateUrl)(defaultIdentityServerUrl)
})),
button: (0, _languageHandler._t)("Continue")
});
prom = finished.then(([useDefault]) => {
if (useDefault) {
(0, _IdentityServerUtils.useDefaultIdentityServer)();
return;
}
throw new Error((0, _languageHandler._t)("Use an identity server to invite by email. Manage in Settings."));
});
} else {
return reject((0, _languageHandler._t)("Use an identity server to invite by email. Manage in Settings."));
}
}
const inviter = new _MultiInviter.default(roomId);
return success(prom.then(() => {
return inviter.invite([address], reason);
}).then(() => {
if (inviter.getCompletionState(address) !== "invited") {
throw new Error(inviter.getErrorText(address));
}
}));
}
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'join',
aliases: ['j', 'goto'],
args: '<room-address>',
description: (0, _languageHandler._td)('Joins room with given address'),
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
// power-user edition where someone may join via permalink or
// room ID with optional servers. Practically, this results
// in the following variations:
// /join #example:example.org
// /join !example:example.org
// /join !example:example.org altserver.com elsewhere.ca
// /join https://matrix.to/#/!example:example.org?via=altserver.com
// The command also supports event permalinks transparently:
// /join https://matrix.to/#/!example:example.org/$something:example.org
// /join https://matrix.to/#/!example:example.org/$something:example.org?via=altserver.com
const params = args.split(' ');
if (params.length < 1) return reject(this.getUsage());
let isPermalink = false;
if (params[0].startsWith("http:") || params[0].startsWith("https:")) {
// It's at least a URL - try and pull out a hostname to check against the
// permalink handler
const parsedUrl = new URL(params[0]);
const hostname = parsedUrl.host || parsedUrl.hostname; // takes first non-falsey value
// if we're using a Element permalink handler, this will catch it before we get much further.
// see below where we make assumptions about parsing the URL.
if ((0, _Permalinks.isPermalinkHost)(hostname)) {
isPermalink = true;
}
}
if (params[0][0] === '#') {
let roomAlias = params[0];
if (!roomAlias.includes(':')) {
roomAlias += ':' + _MatrixClientPeg.MatrixClientPeg.get().getDomain();
}
_dispatcher.default.dispatch({
action: 'view_room',
room_alias: roomAlias,
auto_join: true,
_type: "slash_command" // instrumentation
});
return success();
} else if (params[0][0] === '!') {
const [roomId, ...viaServers] = params;
_dispatcher.default.dispatch({
action: 'view_room',
room_id: roomId,
opts: {
// These are passed down to the js-sdk's /join call
viaServers: viaServers
},
via_servers: viaServers,
// for the rejoin button
auto_join: true,
_type: "slash_command" // instrumentation
});
return success();
} else if (isPermalink) {
const permalinkParts = (0, _Permalinks.parsePermalink)(params[0]); // This check technically isn't needed because we already did our
// safety checks up above. However, for good measure, let's be sure.
if (!permalinkParts) {
return reject(this.getUsage());
} // If for some reason someone wanted to join a group or user, we should
// stop them now.
if (!permalinkParts.roomIdOrAlias) {
return reject(this.getUsage());
}
const entity = permalinkParts.roomIdOrAlias;
const viaServers = permalinkParts.viaServers;
const eventId = permalinkParts.eventId;
const dispatch = {
action: 'view_room',
auto_join: true,
_type: "slash_command" // instrumentation
};
if (entity[0] === '!') dispatch["room_id"] = entity;else dispatch["room_alias"] = entity;
if (eventId) {
dispatch["event_id"] = eventId;
dispatch["highlighted"] = true;
}
if (viaServers) {
// For the join
dispatch["opts"] = {
// These are passed down to the js-sdk's /join call
viaServers: viaServers
}; // For if the join fails (rejoin button)
dispatch['via_servers'] = viaServers;
}
_dispatcher.default.dispatch(dispatch);
return success();
}
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'part',
args: '[<room-address>]',
description: (0, _languageHandler._td)('Leave room'),
runFn: function (roomId, args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
let targetRoomId;
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();
} // Try to find a room with this alias
const rooms = cli.getRooms();
for (let i = 0; i < rooms.length; i++) {
const aliasEvents = rooms[i].currentState.getStateEvents('m.room.aliases');
for (let j = 0; j < aliasEvents.length; j++) {
const aliases = aliasEvents[j].getContent().aliases || [];
for (let k = 0; k < aliases.length; k++) {
if (aliases[k] === roomAlias) {
targetRoomId = rooms[i].roomId;
break;
}
}
if (targetRoomId) break;
}
if (targetRoomId) break;
}
if (!targetRoomId) return reject((0, _languageHandler._t)('Unrecognised room address:') + ' ' + roomAlias);
}
}
if (!targetRoomId) targetRoomId = roomId;
return success((0, _membership.leaveRoomBehaviour)(targetRoomId));
},
category: CommandCategories.actions
}), new Command({
command: 'kick',
args: '<user-id> [reason]',
description: (0, _languageHandler._td)('Kicks user with given id'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(_MatrixClientPeg.MatrixClientPeg.get().kick(roomId, matches[1], matches[3]));
}
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'ban',
args: '<user-id> [reason]',
description: (0, _languageHandler._td)('Bans user with given id'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(_MatrixClientPeg.MatrixClientPeg.get().ban(roomId, matches[1], matches[3]));
}
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'unban',
args: '<user-id>',
description: (0, _languageHandler._td)('Unbans user with given ID'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
return success(_MatrixClientPeg.MatrixClientPeg.get().unban(roomId, matches[1]));
}
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'ignore',
args: '<user-id>',
description: (0, _languageHandler._td)('Ignores a user, hiding their messages from you'),
runFn: function (roomId, args) {
if (args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const matches = args.match(/^(@[^:]+:\S+)$/);
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(() => {
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
title: (0, _languageHandler._t)('Ignored user'),
description: /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("p", null, (0, _languageHandler._t)('You are now ignoring %(userId)s', {
userId
})))
});
}));
}
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'unignore',
args: '<user-id>',
description: (0, _languageHandler._td)('Stops ignoring a user, showing their messages going forward'),
runFn: function (roomId, args) {
if (args) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const matches = args.match(/(^@[^:]+:\S+$)/);
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(() => {
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, {
title: (0, _languageHandler._t)('Unignored user'),
description: /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("p", null, (0, _languageHandler._t)('You are no longer ignoring %(userId)s', {
userId
})))
});
}));
}
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: 'op',
args: '<user-id> [<power-level>]',
description: (0, _languageHandler._td)('Define the power level of a user'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+?)( +(-?\d+))?$/);
let powerLevel = 50; // default power level for op
if (matches) {
const userId = matches[1];
if (matches.length === 4 && undefined !== matches[3]) {
powerLevel = parseInt(matches[3], 10);
}
if (!isNaN(powerLevel)) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (!room) return reject((0, _languageHandler._t)("Command failed"));
const member = room.getMember(userId);
if (!member || (0, _membership.getEffectiveMembership)(member.membership) === _membership.EffectiveMembership.Leave) {
return reject((0, _languageHandler._t)("Could not find user in room"));
}
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
}
}
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'deop',
args: '<user-id>',
description: (0, _languageHandler._td)('Deops user with given id'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (!room) return reject((0, _languageHandler._t)("Command failed"));
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
if (!powerLevelEvent.getContent().users[args]) return reject((0, _languageHandler._t)("Could not find user in room"));
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
}
}
return reject(this.getUsage());
},
category: CommandCategories.admin
}), new Command({
command: 'devtools',
description: (0, _languageHandler._td)('Opens the Developer Tools dialog'),
runFn: function (roomId) {
const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
_Modal.default.createDialog(DevtoolsDialog, {
roomId
});
return success();
},
category: CommandCategories.advanced
}), new Command({
command: 'addwidget',
args: '<url | embed code | Jitsi url>',
description: (0, _languageHandler._td)('Adds a custom widget by URL to the room'),
isEnabled: () => _SettingsStore.default.getValue(_UIFeature.UIFeature.Widgets),
runFn: function (roomId, widgetUrl) {
if (!widgetUrl) {
return reject((0, _languageHandler._t)("Please supply a widget URL or embed code"));
} // Try and parse out a widget URL from iframes
if (widgetUrl.toLowerCase().startsWith("<iframe ")) {
// We use parse5, which doesn't render/create a DOM node. It instead runs
// some superfast regex over the text so we don't have to.
const embed = (0, _parse.parseFragment)(widgetUrl);
if (embed && embed.childNodes && embed.childNodes.length === 1) {
const iframe = embed.childNodes[0];
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
const srcAttr = iframe.attrs.find(a => a.name === 'src');
console.log("Pulling URL out of iframe (embed code)");
widgetUrl = srcAttr.value;
}
}
}
if (!widgetUrl.startsWith("https://") && !widgetUrl.startsWith("http://")) {
return reject((0, _languageHandler._t)("Please supply a https:// or http:// widget URL"));
}
if (_WidgetUtils.default.canUserModifyWidgets(roomId)) {
const userId = _MatrixClientPeg.MatrixClientPeg.get().getUserId();
const nowMs = new Date().getTime();
const widgetId = encodeURIComponent(`${roomId}_${userId}_${nowMs}`);
let type = _WidgetType.WidgetType.CUSTOM;
let name = "Custom Widget";
let data = {}; // Make the widget a Jitsi widget if it looks like a Jitsi widget
const jitsiData = _Jitsi.Jitsi.getInstance().parsePreferredConferenceUrl(widgetUrl);
if (jitsiData) {
console.log("Making /addwidget widget a Jitsi conference");
type = _WidgetType.WidgetType.JITSI;
name = "Jitsi Conference";
data = jitsiData;
widgetUrl = _WidgetUtils.default.getLocalJitsiWrapperUrl();
}
return success(_WidgetUtils.default.setRoomWidget(roomId, widgetId, type, widgetUrl, name, data));
} else {
return reject((0, _languageHandler._t)("You cannot modify widgets in this room."));
}
},
category: CommandCategories.admin
}), new Command({
command: 'verify',
args: '<user-id> <device-id> <device-signing-key>',
description: (0, _languageHandler._td)('Verifies a user, session, and pubkey tuple'),
runFn: function (roomId, args) {
if (args) {
const matches = args.match(/^(\S+) +(\S+) +(\S+)$/);
if (matches) {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const userId = matches[1];
const deviceId = matches[2];
const fingerprint = matches[3];
return success((async () => {
const device = cli.getStoredDevice(userId, deviceId);
if (!device) {
throw new Error((0, _languageHandler._t)('Unknown (user, session) pair:') + ` (${userId}, ${deviceId})`);
}
const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
if (deviceTrust.isVerified()) {
if (device.getFingerprint() === fingerprint) {
throw new Error((0, _languageHandler._t)('Session already verified!'));
} else {
throw new Error((0, _languageHandler._t)('WARNING: Session already verified, but keys do NOT MATCH!'));
}
}
if (device.getFingerprint() !== fingerprint) {
const fprint = device.getFingerprint();
throw new Error((0, _languageHandler._t)('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!', {
fprint,
userId,
deviceId,
fingerprint
}));
}
await cli.setDeviceVerified(userId, deviceId, true); // Tell the user we verified everything
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
title: (0, _languageHandler._t)('Verified key'),
description: /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("p", null, (0, _languageHandler._t)('The signing key you provided matches the signing key you received ' + 'from %(userId)s\'s session %(deviceId)s. Session marked as verified.', {
userId,
deviceId
})))
});
})());
}
}
return reject(this.getUsage());
},
category: CommandCategories.advanced
}), new Command({
command: 'discardsession',
description: (0, _languageHandler._td)('Forces the current outbound group session in an encrypted room to be discarded'),
runFn: function (roomId) {
try {
_MatrixClientPeg.MatrixClientPeg.get().forceDiscardSession(roomId);
} catch (e) {
return reject(e.message);
}
return success();
},
category: CommandCategories.advanced
}), new Command({
command: "rainbow",
description: (0, _languageHandler._td)("Sends the given message coloured as a rainbow"),
args: '<message>',
runFn: function (roomId, args) {
if (!args) return reject(this.getUserId());
return success(ContentHelpers.makeHtmlMessage(args, (0, _colour.textToHtmlRainbow)(args)));
},
category: CommandCategories.messages
}), new Command({
command: "rainbowme",
description: (0, _languageHandler._td)("Sends the given emote coloured as a rainbow"),
args: '<message>',
runFn: function (roomId, args) {
if (!args) return reject(this.getUserId());
return success(ContentHelpers.makeHtmlEmote(args, (0, _colour.textToHtmlRainbow)(args)));
},
category: CommandCategories.messages
}), new Command({
command: "help",
description: (0, _languageHandler._td)("Displays list of commands with usages and descriptions"),
runFn: function () {
const SlashCommandHelpDialog = sdk.getComponent('dialogs.SlashCommandHelpDialog');
_Modal.default.createTrackedDialog('Slash Commands', 'Help', SlashCommandHelpDialog);
return success();
},
category: CommandCategories.advanced
}), new Command({
command: "whois",
description: (0, _languageHandler._td)("Displays information about a user"),
args: "<user-id>",
runFn: function (roomId, userId) {
if (!userId || !userId.startsWith("@") || !userId.includes(":")) {
return reject(this.getUsage());
}
const member = _MatrixClientPeg.MatrixClientPeg.get().getRoom(roomId).getMember(userId);
_dispatcher.default.dispatch({
action: _actions.Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the
// receiver wants.
member: member || {
userId
}
});
return success();
},
category: CommandCategories.advanced
}), new Command({
command: "rageshake",
aliases: ["bugreport"],
description: (0, _languageHandler._td)("Send a bug report with logs"),
isEnabled: () => !!_SdkConfig.default.get().bug_report_endpoint_url,
args: "<description>",
runFn: function (roomId, args) {
return success(_Modal.default.createTrackedDialog('Slash Commands', 'Bug Report Dialog', _BugReportDialog.default, {
initialText: args
}).finished);
},
category: CommandCategories.advanced
}), new Command({
command: "query",
description: (0, _languageHandler._td)("Opens chat with the given user"),
args: "<user-id>",
runFn: function (roomId, userId) {
// 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) {
return reject(this.getUsage());
}
return success((async () => {
if (isPhoneNumber) {
const results = await _CallHandler.default.sharedInstance().pstnLookup(this.state.value);
if (!results || results.length === 0 || !results[0].userid) {
throw new Error("Unable to find Matrix ID for phone number");
}
userId = results[0].userid;
}
const roomId = await (0, _createRoom.ensureDMExists)(_MatrixClientPeg.MatrixClientPeg.get(), userId);
_dispatcher.default.dispatch({
action: 'view_room',
room_id: roomId
});
})());
},
category: CommandCategories.actions
}), new Command({
command: "msg",
description: (0, _languageHandler._td)("Sends a message to the given user"),
args: "<user-id> <message>",
runFn: function (_, args) {
if (args) {
// matches the first whitespace delimited group and then the rest of the string
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);
if (matches) {
const [userId, msg] = matches.slice(1);
if (msg && userId && userId.startsWith("@") && userId.includes(":")) {
return success((async () => {
const cli = _MatrixClientPeg.MatrixClientPeg.get();
const roomId = await (0, _createRoom.ensureDMExists)(cli, userId);
_dispatcher.default.dispatch({
action: 'view_room',
room_id: roomId
});
cli.sendTextMessage(roomId, msg);
})());
}
}
}
return reject(this.getUsage());
},
category: CommandCategories.actions
}), new Command({
command: "holdcall",
description: (0, _languageHandler._td)("Places the call in the current room on hold"),
category: CommandCategories.other,
runFn: function (roomId, args) {
const call = _CallHandler.default.sharedInstance().getCallForRoom(roomId);
if (!call) {
return reject("No active call in this room");
}
call.setRemoteOnHold(true);
return success();
}
}), new Command({
command: "unholdcall",
description: (0, _languageHandler._td)("Takes the call in the current room off hold"),
category: CommandCategories.other,
runFn: function (roomId, args) {
const call = _CallHandler.default.sharedInstance().getCallForRoom(roomId);
if (!call) {
return reject("No active call in this room");
}
call.setRemoteOnHold(false);
return success();
}
}), new Command({
command: "converttodm",
description: (0, _languageHandler._td)("Converts the room to a DM"),
category: CommandCategories.other,
runFn: function (roomId, args) {
const room = _MatrixClientPeg.MatrixClientPeg.get().getRoom(roomId);
return success((0, _Rooms.guessAndSetDMRoom)(room, true));
}
}), new Command({
command: "converttoroom",
description: (0, _languageHandler._td)("Converts the DM to a room"),
category: CommandCategories.other,
runFn: function (roomId, args) {
const room = _MatrixClientPeg.MatrixClientPeg.get().getRoom(roomId);
return success((0, _Rooms.guessAndSetDMRoom)(room, false));
}
}), // 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: '<message>',
description: (0, _languageHandler._td)('Displays action'),
category: CommandCategories.messages,
hideCompletionAfterSpace: true
}), ..._effects.CHAT_EFFECTS.map(effect => {
return new Command({
command: effect.command,
description: effect.description(),
args: '<message>',
runFn: function (roomId, args) {
return success((async () => {
if (!args) {
args = effect.fallbackMessage();
_MatrixClientPeg.MatrixClientPeg.get().sendEmoteMessage(roomId, args);
} else {
const content = {
msgtype: effect.msgType,
body: args
};
_MatrixClientPeg.MatrixClientPeg.get().sendMessage(roomId, content);
}
_dispatcher.default.dispatch({
action: `effects.${effect.command}`
});
})());
},
category: CommandCategories.effects
});
})]; // build a map from names and aliases to the Command objects.
exports.Commands = Commands;
const CommandMap = new Map();
exports.CommandMap = CommandMap;
Commands.forEach(cmd => {
CommandMap.set(cmd.command, cmd);
cmd.aliases.forEach(alias => {
CommandMap.set(alias, cmd);
});
});
function parseCommandString(input
/*: string*/
) {
// trim any trailing whitespace, as it can confuse the parser for
// IRC-style commands
input = input.replace(/\s+$/, '');
if (input[0] !== '/') return {}; // not a command
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
let cmd;
let args;
if (bits) {
cmd = bits[1].substring(1).toLowerCase();
args = bits[2];
} else {
cmd = input;
}
return {
cmd,
args
};
}
/**
* Process the given text for /commands and return a bound method to perform them.
* @param {string} roomId The room in which the command was performed.
* @param {string} input The raw text input by the user.
* @return {null|function(): Object} Function returning an object with the property 'error' if there was an error
* processing the command, or 'promise' if a request was sent out.
* Returns null if the input didn't match a command.
*/
function getCommand(input
/*: string*/
) {
const {
cmd,
args
} = parseCommandString(input);
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
return {
cmd: CommandMap.get(cmd),
args
};
}
return {};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9TbGFzaENvbW1hbmRzLnRzeCJdLCJuYW1lcyI6WyJzaW5nbGVNeGNVcGxvYWQiLCJQcm9taXNlIiwicmVzb2x2ZSIsImZpbGVTZWxlY3RvciIsImRvY3VtZW50IiwiY3JlYXRlRWxlbWVudCIsInNldEF0dHJpYnV0ZSIsIm9uY2hhbmdlIiwiZXYiLCJmaWxlIiwidGFyZ2V0IiwiZmlsZXMiLCJVcGxvYWRDb25maXJtRGlhbG9nIiwic2RrIiwiZ2V0Q29tcG9uZW50IiwiTW9kYWwiLCJjcmVhdGVUcmFja2VkRGlhbG9nIiwib25GaW5pc2hlZCIsInNob3VsZENvbnRpbnVlIiwiTWF0cml4Q2xpZW50UGVnIiwiZ2V0IiwidXBsb2FkQ29udGVudCIsImNsaWNrIiwiQ29tbWFuZENhdGVnb3JpZXMiLCJDb21tYW5kIiwiY29uc3RydWN0b3IiLCJvcHRzIiwiY29tbWFuZCIsImFsaWFzZXMiLCJhcmdzIiwiZGVzY3JpcHRpb24iLCJydW5GbiIsImNhdGVnb3J5Iiwib3RoZXIiLCJoaWRlQ29tcGxldGlvbkFmdGVyU3BhY2UiLCJfaXNFbmFibGVkIiwiaXNFbmFibGVkIiwiZ2V0Q29tbWFuZCIsImdldENvbW1hbmRXaXRoQXJncyIsInJ1biIsInJvb21JZCIsInJlamVjdCIsImJpbmQiLCJnZXRVc2FnZSIsImVycm9yIiwic3VjY2VzcyIsInByb21pc2UiLCJDb21tYW5kcyIsIm1lc3NhZ2UiLCJDb250ZW50SGVscGVycyIsIm1ha2VIdG1sTWVzc2FnZSIsIm1lc3NhZ2VzIiwibWFrZVRleHRNZXNzYWdlIiwiRXJyb3JEaWFsb2ciLCJ0aXRsZSIsImFjdGlvbnMiLCJjbGkiLCJyb29tIiwiZ2V0Um9vbSIsImN1cnJlbnRTdGF0ZSIsIm1heUNsaWVudFNlbmRTdGF0ZUV2ZW50IiwiUm9vbVVwZ3JhZGVXYXJuaW5nRGlhbG9nIiwiZmluaXNoZWQiLCJ0YXJnZXRWZXJzaW9uIiwidGhlbiIsInJlc3AiLCJjb250aW51ZSIsImNoZWNrRm9yVXBncmFkZUZuIiwidXBncmFkZVByb21pc2UiLCJ1cGdyYWRlUm9vbSIsImludml0ZSIsIm5ld1Jvb20iLCJyZXBsYWNlbWVudF9yb29tIiwibmV3Um9vbUlkIiwidG9JbnZpdGUiLCJnZXRNZW1iZXJzV2l0aE1lbWJlcnNoaXAiLCJtYXAiLCJtIiwidXNlcklkIiwiZmlsdGVyIiwiZ2V0VXNlcklkIiwibGVuZ3RoIiwicmVtb3ZlTGlzdGVuZXIiLCJvbiIsImUiLCJjb25zb2xlIiwiYWRtaW4iLCJzZXREaXNwbGF5TmFtZSIsImdldFN0YXRlRXZlbnRzIiwiY29udGVudCIsImdldENvbnRlbnQiLCJtZW1iZXJzaGlwIiwiZGlzcGxheW5hbWUiLCJzZW5kU3RhdGVFdmVudCIsInVybCIsImF2YXRhcl91cmwiLCJzZXRBdmF0YXJVcmwiLCJzZXRSb29tVG9waWMiLCJ0b3BpY0V2ZW50cyIsInRvcGljIiwidG9waWNIdG1sIiwiSW5mb0RpYWxvZyIsIm5hbWUiLCJfX2h0bWwiLCJoYXNDbG9zZUJ1dHRvbiIsInNldFJvb21OYW1lIiwiYWRkcmVzcyIsInJlYXNvbiIsInNwbGl0IiwicHJvbSIsImdldElkZW50aXR5U2VydmVyVXJsIiwiZGVmYXVsdElkZW50aXR5U2VydmVyVXJsIiwiUXVlc3Rpb25EaWFsb2ciLCJkZWZhdWx0SWRlbnRpdHlTZXJ2ZXJOYW1lIiwiYnV0dG9uIiwidXNlRGVmYXVsdCIsIkVycm9yIiwiaW52aXRlciIsIk11bHRpSW52aXRlciIsImdldENvbXBsZXRpb25TdGF0ZSIsImdldEVycm9yVGV4dCIsIl8iLCJwYXJhbXMiLCJpc1Blcm1hbGluayIsInN0YXJ0c1dpdGgiLCJwYXJzZWRVcmwiLCJVUkwiLCJob3N0bmFtZSIsImhvc3QiLCJyb29tQWxpYXMiLCJpbmNsdWRlcyIsImdldERvbWFpbiIsImRpcyIsImRpc3BhdGNoIiwiYWN0aW9uIiwicm9vbV9hbGlhcyIsImF1dG9fam9pbiIsIl90eXBlIiwidmlhU2VydmVycyIsInJvb21faWQiLCJ2aWFfc2VydmVycyIsInBlcm1hbGlua1BhcnRzIiwicm9vbUlkT3JBbGlhcyIsImVudGl0eSIsImV2ZW50SWQiLCJ0YXJnZXRSb29tSWQiLCJtYXRjaGVzIiwibWF0Y2giLCJyb29tcyIsImdldFJvb21zIiwiaSIsImFsaWFzRXZlbnRzIiwiaiIsImsiLCJraWNrIiwiYmFuIiwidW5iYW4iLCJpZ25vcmVkVXNlcnMiLCJnZXRJZ25vcmVkVXNlcnMiLCJwdXNoIiwic2V0SWdub3JlZFVzZXJzIiwiaW5kZXgiLCJpbmRleE9mIiwic3BsaWNlIiwicG93ZXJMZXZlbCIsInVuZGVmaW5lZCIsInBhcnNlSW50IiwiaXNOYU4iLCJtZW1iZXIiLCJnZXRNZW1iZXIiLCJFZmZlY3RpdmVNZW1iZXJzaGlwIiwiTGVhdmUiLCJwb3dlckxldmVsRXZlbnQiLCJzZXRQb3dlckxldmVsIiwidXNlcnMiLCJEZXZ0b29sc0RpYWxvZyIsImNyZWF0ZURpYWxvZyIsImFkdmFuY2VkIiwiU2V0dGluZ3NTdG9yZSIsImdldFZhbHVlIiwiVUlGZWF0dXJlIiwiV2lkZ2V0cyIsIndpZGdldFVybCIsInRvTG93ZXJDYXNlIiwiZW1iZWQiLCJjaGlsZE5vZGVzIiwiaWZyYW1lIiwidGFnTmFtZSIsImF0dHJzIiwic3JjQXR0ciIsImZpbmQiLCJhIiwibG9nIiwidmFsdWUiLCJXaWRnZXRVdGlscyIsImNhblVzZXJNb2RpZnlXaWRnZXRzIiwibm93TXMiLCJEYXRlIiwiZ2V0VGltZSIsIndpZGdldElkIiwiZW5jb2RlVVJJQ29tcG9uZW50IiwidHlwZSIsIldpZGdldFR5cGUiLCJDVVNUT00iLCJkYXRhIiwiaml0c2lEYXRhIiwiSml0c2kiLCJnZXRJbnN0YW5jZSIsInBhcnNlUHJlZmVycmVkQ29uZmVyZW5jZVVybCIsIkpJVFNJIiwiZ2V0TG9jYWxKaXRzaVdyYXBwZXJVcmwiLCJzZXRSb29tV2lkZ2V0IiwiZGV2aWNlSWQiLCJmaW5nZXJwcmludCIsImRldmljZSIsImdldFN0b3JlZERldmljZSIsImRldmljZVRydXN0IiwiY2hlY2tEZXZpY2VUcnVzdCIsImlzVmVyaWZpZWQiLCJnZXRGaW5nZXJwcmludCIsImZwcmludCIsInNldERldmljZVZlcmlmaWVkIiwiZm9yY2VEaXNjYXJkU2Vzc2lvbiIsIm1ha2VIdG1sRW1vdGUiLCJTbGFzaENvbW1hbmRIZWxwRGlhbG9nIiwiQWN0aW9uIiwiVmlld1VzZXIiLCJTZGtDb25maWciLCJidWdfcmVwb3J0X2VuZHBvaW50X3VybCIsIkJ1Z1JlcG9ydERpYWxvZyIsImluaXRpYWxUZXh0IiwiaXNQaG9uZU51bWJlciIsInRlc3QiLCJyZXN1bHRzIiwiQ2FsbEhhbmRsZXIiLCJzaGFyZWRJbnN0YW5jZSIsInBzdG5Mb29rdXAiLCJzdGF0ZSIsInVzZXJpZCIsIm1zZyIsInNsaWNlIiwic2VuZFRleHRNZXNzYWdlIiwiY2FsbCIsImdldENhbGxGb3JSb29tIiwic2V0UmVtb3RlT25Ib2xkIiwiQ0hBVF9FRkZFQ1RTIiwiZWZmZWN0IiwiZmFsbGJhY2tNZXNzYWdlIiwic2VuZEVtb3RlTWVzc2FnZSIsIm1zZ3R5cGUiLCJtc2dUeXBlIiwiYm9keSIsInNlbmRNZXNzYWdlIiwiZWZmZWN0cyIsIkNvbW1hbmRNYXAiLCJNYXAiLCJmb3JFYWNoIiwiY21kIiwic2V0IiwiYWxpYXMiLCJwYXJzZUNvbW1hbmRTdHJpbmciLCJpbnB1dCIsInJlcGxhY2UiLCJiaXRzIiwic3Vic3RyaW5nIiwiaGFzIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7QUFvQkE7O0FBRUE7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBRUE7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7O0FBQ0E7Ozs7OztBQU9BLE1BQU1BLGVBQWUsR0FBRztBQUFBO0FBQTBCO0FBQzlDLFNBQU8sSUFBSUMsT0FBSixDQUFhQyxPQUFELElBQWE7QUFDNUIsVUFBTUMsWUFBWSxHQUFHQyxRQUFRLENBQUNDLGFBQVQsQ0FBdUIsT0FBdkIsQ0FBckI7QUFDQUYsSUFBQUEsWUFBWSxDQUFDRyxZQUFiLENBQTBCLE1BQTFCLEVBQWtDLE1BQWxDOztBQUNBSCxJQUFBQSxZQUFZLENBQUNJLFFBQWIsR0FBd0IsQ0FBQ0M7QUFBRDtBQUFBLFNBQXdCO0FBQzVDLFlBQU1DLElBQUksR0FBR0QsRUFBRSxDQUFDRSxNQUFILENBQVVDLEtBQVYsQ0FBZ0IsQ0FBaEIsQ0FBYjtBQUVBLFlBQU1DLG1CQUFtQixHQUFHQyxHQUFHLENBQUNDLFlBQUosQ0FBaUIsNkJBQWpCLENBQTVCOztBQUNBQyxxQkFBTUMsbUJBQU4sQ0FBMEIsMkJBQTFCLEVBQXVELEVBQXZELEVBQTJESixtQkFBM0QsRUFBZ0Y7QUFDNUVILFFBQUFBLElBRDRFO0FBRTVFUSxRQUFBQSxVQUFVLEVBQUdDLGNBQUQsSUFBb0I7QUFDNUJoQixVQUFBQSxPQUFPLENBQ