Add support for slash commands (under Feature Flag) (#6482)

* Add support for slash commands

* Update screenshots

* Rename module `slash` to `slashcommands`

* Rename `SlashCommand` to `SlashCommandService`

* Introduce MsgType in order to send text message with a different msgtype value.

* Format file and add parameter names, add default values and cleanup

* Add isSupported parameter to filter out unsupported yet commands.

* Slash commands: disable suggestions if the feature is disabled.

* Fix sending shrug command.

* Add missing test on SuggestionsProcessor

* Add tests on MessageComposerPresenter about slash command.

* Fix import ordering

* Add missing tests on CommandExecutor

* Add missing tests in MarkdownTextEditorStateTest

* Slash commands: Improve code when sending message with prefix.

* Slash commands: Add support for /unflip

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Benoit Marty 2026-04-02 16:15:32 +02:00 committed by GitHub
parent f08d1ed686
commit a77662421c
65 changed files with 3038 additions and 86 deletions

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.slashcommands.api"
}
dependencies {
implementation(projects.libraries.matrix.api)
}

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.slashcommands.api
enum class ChatEffect {
CONFETTI,
SNOWFALL
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.slashcommands.api
enum class MessagePrefix {
Shrug,
TableFlip,
Unflip,
Lenny,
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.slashcommands.api
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId
/**
* Represent a slash command.
*/
sealed interface SlashCommand {
// This is not a Slash command
data object NotACommand : SlashCommand
// Slash command types:
sealed interface Error : SlashCommand
sealed interface SlashCommandSendMessage : SlashCommand
sealed interface SlashCommandAdmin : SlashCommand
sealed interface SlashCommandNavigation : SlashCommand
// Errors
data class ErrorEmptySlashCommand(val message: String) : Error
data class ErrorCommandNotSupportedInThreads(val message: String) : Error
// Unknown/Unsupported slash command
data class ErrorUnknownSlashCommand(val message: String) : Error
// A slash command is detected, but there is an error
data class ErrorSyntax(val message: String) : Error
// Valid commands:
data class SendPlainText(val message: CharSequence) : SlashCommandSendMessage
data class SendEmote(val message: CharSequence) : SlashCommandSendMessage
data class SendRainbow(val message: CharSequence) : SlashCommandSendMessage
data class SendRainbowEmote(val message: CharSequence) : SlashCommandSendMessage
data class BanUser(val userId: UserId, val reason: String?) : SlashCommandAdmin
data class UnbanUser(val userId: UserId, val reason: String?) : SlashCommandAdmin
data class IgnoreUser(val userId: UserId) : SlashCommandAdmin
data class UnignoreUser(val userId: UserId) : SlashCommandAdmin
data class SetUserPowerLevel(val userId: UserId, val powerLevel: Int?) : SlashCommandAdmin
data class ChangeRoomName(val name: String) : SlashCommandAdmin
data class Invite(val userId: UserId, val reason: String?) : SlashCommandAdmin
data class JoinRoom(val roomIdOrAlias: RoomIdOrAlias, val reason: String?) : SlashCommandAdmin
data class ChangeTopic(val topic: String) : SlashCommandAdmin
data class RemoveUser(val userId: UserId, val reason: String?) : SlashCommandAdmin
data class ChangeDisplayName(val displayName: String) : SlashCommandAdmin
data class ChangeDisplayNameForRoom(val displayName: String) : SlashCommandAdmin
data class ChangeRoomAvatar(val url: String) : SlashCommandAdmin
data class ChangeAvatarForRoom(val url: String) : SlashCommandAdmin
data class SendSpoiler(val message: String) : SlashCommandSendMessage
data class SendWithPrefix(val prefix: MessagePrefix, val message: CharSequence) : SlashCommandSendMessage
data object DiscardSession : SlashCommandAdmin
data class SendChatEffect(val chatEffect: ChatEffect, val message: String) : SlashCommandSendMessage
data object LeaveRoom : SlashCommandAdmin
data class UpgradeRoom(val newVersion: String) : SlashCommandAdmin
data object DevTools : SlashCommandNavigation
data class ShowUser(val userId: UserId) : SlashCommandNavigation
}
fun SlashCommand.Error.message() = when (this) {
is SlashCommand.ErrorEmptySlashCommand -> message
is SlashCommand.ErrorCommandNotSupportedInThreads -> message
is SlashCommand.ErrorUnknownSlashCommand -> message
is SlashCommand.ErrorSyntax -> message
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.slashcommands.api
import io.element.android.libraries.matrix.api.timeline.Timeline
interface SlashCommandService {
suspend fun getSuggestions(
text: String,
isInThread: Boolean,
): List<SlashCommandSuggestion>
/**
* Parse the message and return a SlashCommand.
*/
suspend fun parse(
textMessage: CharSequence,
formattedMessage: String?,
isInThreadTimeline: Boolean,
): SlashCommand
/**
* Proceed a SlashCommandSendMessage.
*/
suspend fun proceedSendMessage(
slashCommand: SlashCommand.SlashCommandSendMessage,
timeline: Timeline,
): Result<Unit>
/**
* Proceed a SlashCommandAdmin.
*/
suspend fun proceedAdmin(
slashCommand: SlashCommand.SlashCommandAdmin,
): Result<Unit>
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.slashcommands.api
data class SlashCommandSuggestion(
val command: String,
val parameters: String?,
val description: String,
)