Merge pull request #4268 from vector-im/feature/bma/improve_part

A few changes on the slash command
This commit is contained in:
Benoit Marty 2021-10-19 17:01:25 +02:00 committed by GitHub
commit 86b7fe67ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 64 additions and 63 deletions

1
changelog.d/2909.feature Normal file
View File

@ -0,0 +1 @@
Implement /part command, with or without parameter

1
changelog.d/4261.bugfix Normal file
View File

@ -0,0 +1 @@
Fix crash on slash commands Exceptions

View File

@ -34,8 +34,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user, false), RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user, false),
ROOM_NAME("/roomname", "<name>", R.string.command_description_room_name, false), ROOM_NAME("/roomname", "<name>", R.string.command_description_room_name, false),
INVITE("/invite", "<user-id> [reason]", R.string.command_description_invite_user, false), INVITE("/invite", "<user-id> [reason]", R.string.command_description_invite_user, false),
JOIN_ROOM("/join", "<room-alias> [reason]", R.string.command_description_join_room, false), JOIN_ROOM("/join", "<room-address> [reason]", R.string.command_description_join_room, false),
PART("/part", "<room-alias> [reason]", R.string.command_description_part_room, false), PART("/part", "[<room-address>]", R.string.command_description_part_room, false),
TOPIC("/topic", "<topic>", R.string.command_description_topic, false), TOPIC("/topic", "<topic>", R.string.command_description_topic, false),
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user, false), KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user, false),
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick, false), CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick, false),

View File

@ -158,21 +158,10 @@ object CommandParser {
} }
} }
Command.PART.command -> { Command.PART.command -> {
if (messageParts.size >= 2) { when (messageParts.size) {
val roomAlias = messageParts[1] 1 -> ParsedCommand.PartRoom(null)
2 -> ParsedCommand.PartRoom(messageParts[1])
if (roomAlias.isNotEmpty()) { else -> ParsedCommand.ErrorSyntax(Command.PART)
ParsedCommand.PartRoom(
roomAlias,
textMessage.substring(Command.PART.length + roomAlias.length)
.trim()
.takeIf { it.isNotBlank() }
)
} else {
ParsedCommand.ErrorSyntax(Command.PART)
}
} else {
ParsedCommand.ErrorSyntax(Command.PART)
} }
} }
Command.ROOM_NAME.command -> { Command.ROOM_NAME.command -> {

View File

@ -49,7 +49,7 @@ sealed class ParsedCommand {
class Invite(val userId: String, val reason: String?) : ParsedCommand() class Invite(val userId: String, val reason: String?) : ParsedCommand()
class Invite3Pid(val threePid: ThreePid) : ParsedCommand() class Invite3Pid(val threePid: ThreePid) : ParsedCommand()
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class PartRoom(val roomAlias: String?) : ParsedCommand()
class ChangeTopic(val topic: String) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand()
class KickUser(val userId: String, val reason: String?) : ParsedCommand() class KickUser(val userId: String, val reason: String?) : ParsedCommand()
class ChangeDisplayName(val displayName: String) : ParsedCommand() class ChangeDisplayName(val displayName: String) : ParsedCommand()

View File

@ -1451,8 +1451,8 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is TextComposerViewEvents.SlashCommandHandled -> { is TextComposerViewEvents.SlashCommandLoading -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } showLoading(null)
} }
is TextComposerViewEvents.SlashCommandError -> { is TextComposerViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
@ -1461,9 +1461,12 @@ class RoomDetailFragment @Inject constructor(
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
} }
is TextComposerViewEvents.SlashCommandResultOk -> { is TextComposerViewEvents.SlashCommandResultOk -> {
dismissLoadingDialog()
views.composerLayout.setTextIfDifferent("") views.composerLayout.setTextIfDifferent("")
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
} }
is TextComposerViewEvents.SlashCommandResultError -> { is TextComposerViewEvents.SlashCommandResultError -> {
dismissLoadingDialog()
displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
} }
is TextComposerViewEvents.SlashCommandNotImplemented -> { is TextComposerViewEvents.SlashCommandNotImplemented -> {

View File

@ -32,8 +32,8 @@ sealed class TextComposerViewEvents : VectorViewEvents {
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult()
class SlashCommandUnknown(val command: String) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult()
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() object SlashCommandLoading : SendMessageResult()
object SlashCommandResultOk : SendMessageResult() data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult()
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents() data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents()

View File

@ -176,19 +176,15 @@ class TextComposerViewModel @AssistedInject constructor(
} }
is ParsedCommand.ChangeRoomName -> { is ParsedCommand.ChangeRoomName -> {
handleChangeRoomNameSlashCommand(slashCommandResult) handleChangeRoomNameSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.Invite -> { is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult) handleInviteSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.Invite3Pid -> { is ParsedCommand.Invite3Pid -> {
handleInvite3pidSlashCommand(slashCommandResult) handleInvite3pidSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.SetUserPowerLevel -> { is ParsedCommand.SetUserPowerLevel -> {
handleSetUserPowerLevel(slashCommandResult) handleSetUserPowerLevel(slashCommandResult)
popDraft()
} }
is ParsedCommand.ClearScalarToken -> { is ParsedCommand.ClearScalarToken -> {
// TODO // TODO
@ -196,55 +192,49 @@ class TextComposerViewModel @AssistedInject constructor(
} }
is ParsedCommand.SetMarkdown -> { is ParsedCommand.SetMarkdown -> {
vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) vectorPreferences.setMarkdownEnabled(slashCommandResult.enable)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled( _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk(
if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
popDraft() popDraft()
} }
is ParsedCommand.BanUser -> { is ParsedCommand.BanUser -> {
handleBanSlashCommand(slashCommandResult) handleBanSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.UnbanUser -> { is ParsedCommand.UnbanUser -> {
handleUnbanSlashCommand(slashCommandResult) handleUnbanSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.IgnoreUser -> { is ParsedCommand.IgnoreUser -> {
handleIgnoreSlashCommand(slashCommandResult) handleIgnoreSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.UnignoreUser -> { is ParsedCommand.UnignoreUser -> {
handleUnignoreSlashCommand(slashCommandResult) handleUnignoreSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.KickUser -> { is ParsedCommand.KickUser -> {
handleKickSlashCommand(slashCommandResult) handleKickSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.JoinRoom -> { is ParsedCommand.JoinRoom -> {
handleJoinToAnotherRoomSlashCommand(slashCommandResult) handleJoinToAnotherRoomSlashCommand(slashCommandResult)
popDraft() popDraft()
} }
is ParsedCommand.PartRoom -> { is ParsedCommand.PartRoom -> {
// TODO handlePartSlashCommand(slashCommandResult)
_viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented)
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendRainbow -> { is ParsedCommand.SendRainbow -> {
slashCommandResult.message.toString().let { slashCommandResult.message.toString().let {
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) room.sendFormattedTextMessage(it, rainbowGenerator.generate(it))
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendRainbowEmote -> { is ParsedCommand.SendRainbowEmote -> {
slashCommandResult.message.toString().let { slashCommandResult.message.toString().let {
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE)
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendSpoiler -> { is ParsedCommand.SendSpoiler -> {
@ -252,61 +242,56 @@ class TextComposerViewModel @AssistedInject constructor(
"[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})",
"<span data-mx-spoiler>${slashCommandResult.message}</span>" "<span data-mx-spoiler>${slashCommandResult.message}</span>"
) )
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendShrug -> { is ParsedCommand.SendShrug -> {
sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message) sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendLenny -> { is ParsedCommand.SendLenny -> {
sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message) sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendChatEffect -> { is ParsedCommand.SendChatEffect -> {
sendChatEffect(slashCommandResult) sendChatEffect(slashCommandResult)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.SendPoll -> { is ParsedCommand.SendPoll -> {
room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") }) room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") })
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
is ParsedCommand.ChangeTopic -> { is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult) handleChangeTopicSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.ChangeDisplayName -> { is ParsedCommand.ChangeDisplayName -> {
handleChangeDisplayNameSlashCommand(slashCommandResult) handleChangeDisplayNameSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.ChangeDisplayNameForRoom -> { is ParsedCommand.ChangeDisplayNameForRoom -> {
handleChangeDisplayNameForRoomSlashCommand(slashCommandResult) handleChangeDisplayNameForRoomSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.ChangeRoomAvatar -> { is ParsedCommand.ChangeRoomAvatar -> {
handleChangeRoomAvatarSlashCommand(slashCommandResult) handleChangeRoomAvatarSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.ChangeAvatarForRoom -> { is ParsedCommand.ChangeAvatarForRoom -> {
handleChangeAvatarForRoomSlashCommand(slashCommandResult) handleChangeAvatarForRoomSlashCommand(slashCommandResult)
popDraft()
} }
is ParsedCommand.ShowUser -> { is ParsedCommand.ShowUser -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
handleWhoisSlashCommand(slashCommandResult) handleWhoisSlashCommand(slashCommandResult)
popDraft() popDraft()
} }
is ParsedCommand.DiscardSession -> { is ParsedCommand.DiscardSession -> {
if (room.isEncrypted()) { if (room.isEncrypted()) {
session.cryptoService().discardOutboundSession(room.roomId) session.cryptoService().discardOutboundSession(room.roomId)
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} else { } else {
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
_viewEvents.post( _viewEvents.post(
TextComposerViewEvents TextComposerViewEvents
.ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled)) .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled))
@ -314,6 +299,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
} }
is ParsedCommand.CreateSpace -> { is ParsedCommand.CreateSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val params = CreateSpaceParams().apply { val params = CreateSpaceParams().apply {
@ -328,14 +314,16 @@ class TextComposerViewModel @AssistedInject constructor(
null, null,
true true
) )
popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure))
} }
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) Unit
popDraft()
} }
is ParsedCommand.AddToSpace -> { is ParsedCommand.AddToSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
session.spaceService().getSpace(slashCommandResult.spaceId) session.spaceService().getSpace(slashCommandResult.spaceId)
@ -345,34 +333,38 @@ class TextComposerViewModel @AssistedInject constructor(
null, null,
false false
) )
popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure))
} }
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) Unit
popDraft()
} }
is ParsedCommand.JoinSpace -> { is ParsedCommand.JoinSpace -> {
_viewEvents.post(TextComposerViewEvents.SlashCommandLoading)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias)
popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure))
} }
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) Unit
popDraft()
} }
is ParsedCommand.LeaveRoom -> { is ParsedCommand.LeaveRoom -> {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
session.getRoom(slashCommandResult.roomId)?.leave(null) session.getRoom(slashCommandResult.roomId)?.leave(null)
popDraft()
_viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure))
} }
} }
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) Unit
popDraft()
} }
is ParsedCommand.UpgradeRoom -> { is ParsedCommand.UpgradeRoom -> {
_viewEvents.post( _viewEvents.post(
@ -381,7 +373,7 @@ class TextComposerViewModel @AssistedInject constructor(
room.roomSummary()?.isPublic ?: false room.roomSummary()?.isPublic ?: false
) )
) )
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk())
popDraft() popDraft()
} }
}.exhaustive }.exhaustive
@ -578,6 +570,20 @@ class TextComposerViewModel @AssistedInject constructor(
} }
} }
private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
launchSlashCommandFlowSuspendable {
if (command.roomAlias == null) {
// Leave the current room
room
} else {
session.getRoomSummary(roomIdOrAlias = command.roomAlias)
?.roomId
?.let { session.getRoom(it) }
}
?.leave(reason = null)
}
}
private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) { private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) {
launchSlashCommandFlowSuspendable { launchSlashCommandFlowSuspendable {
room.kick(kick.userId, kick.reason) room.kick(kick.userId, kick.reason)
@ -690,12 +696,13 @@ class TextComposerViewModel @AssistedInject constructor(
} }
private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) {
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) _viewEvents.post(TextComposerViewEvents.SlashCommandLoading)
viewModelScope.launch { viewModelScope.launch {
val event = try { val event = try {
block() block()
TextComposerViewEvents.SlashCommandResultOk popDraft()
} catch (failure: Exception) { TextComposerViewEvents.SlashCommandResultOk()
} catch (failure: Throwable) {
TextComposerViewEvents.SlashCommandResultError(failure) TextComposerViewEvents.SlashCommandResultError(failure)
} }
_viewEvents.post(event) _viewEvents.post(event)

View File

@ -1837,7 +1837,7 @@
<string name="command_description_deop_user">Deops user with given id</string> <string name="command_description_deop_user">Deops user with given id</string>
<string name="command_description_room_name">Sets the room name</string> <string name="command_description_room_name">Sets the room name</string>
<string name="command_description_invite_user">Invites user with given id to current room</string> <string name="command_description_invite_user">Invites user with given id to current room</string>
<string name="command_description_join_room">Joins room with given alias</string> <string name="command_description_join_room">Joins room with given address</string>
<string name="command_description_part_room">Leave room</string> <string name="command_description_part_room">Leave room</string>
<string name="command_description_topic">Set the room topic</string> <string name="command_description_topic">Set the room topic</string>
<string name="command_description_kick_user">Kicks user with given id</string> <string name="command_description_kick_user">Kicks user with given id</string>