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:
parent
f08d1ed686
commit
a77662421c
65 changed files with 3038 additions and 86 deletions
17
libraries/slashcommands/api/build.gradle.kts
Normal file
17
libraries/slashcommands/api/build.gradle.kts
Normal 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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue