diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index f5e115d..c224ad5 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.kotlin/errors/errors-1731969919007.log b/.kotlin/errors/errors-1731969919007.log new file mode 100644 index 0000000..1219b50 --- /dev/null +++ b/.kotlin/errors/errors-1731969919007.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.21 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/Dispatch.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/Dispatch.kt index 6e80352..55efc25 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/Dispatch.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/Dispatch.kt @@ -1,13 +1,10 @@ package com.github.alice.ktx -import com.github.alice.ktx.api.dialog.DialogApi +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.handlers.Handler import com.github.alice.ktx.handlers.error.NetworkErrorHandler import com.github.alice.ktx.middleware.Middleware import com.github.alice.ktx.middleware.MiddlewareType -import com.github.alice.ktx.models.FSMStrategy -import com.github.alice.ktx.models.request.MessageRequest -import com.github.alice.ktx.context.FSMContext /** * Расширение для `Skill.Builder`, позволяющее настроить `Dispatcher`. @@ -15,21 +12,13 @@ import com.github.alice.ktx.context.FSMContext * @param body Функция, принимающая объект `Dispatcher` и позволяющая настроить его. * Эта функция будет вызвана в контексте `Dispatcher`. */ +@AliceDsl fun Skill.Builder.dispatch(body: Dispatcher.() -> Unit) { dispatcherConfiguration = body } -/** - * Класс `Dispatcher` управляет обработчиками команд, обработчиками сетевых ошибок и мидлварами. - * - * @param fsmStrategy Стратегия конечного автомата состояний (FSM), используемая для управления состояниями. - */ -class Dispatcher internal constructor( - internal val fsmStrategy: FSMStrategy, - val dialogApi: DialogApi? = null, - internal val fsmContext: (message: MessageRequest) -> FSMContext, - internal val enableApiStorage: Boolean = false -) { +@AliceDsl +class Dispatcher internal constructor() { internal val commandHandlers = linkedSetOf() internal val networkErrorHandlers = linkedSetOf() internal val middlewares = mutableMapOf>() diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/Skill.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/Skill.kt index 981f462..4d48462 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/Skill.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/Skill.kt @@ -1,17 +1,17 @@ package com.github.alice.ktx import com.github.alice.ktx.api.dialog.DialogApi +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.middleware.MiddlewareType import com.github.alice.ktx.models.FSMStrategy -import com.github.alice.ktx.models.Request -import com.github.alice.ktx.models.request import com.github.alice.ktx.models.request.MessageRequest import com.github.alice.ktx.models.response.MessageResponse -import com.github.alice.ktx.models.toEventRequest import com.github.alice.ktx.server.WebServer -import com.github.alice.ktx.server.WebServerResponseListener -import com.github.alice.ktx.context.FSMContext -import com.github.alice.ktx.context.impl.BaseFSMContext +import com.github.alice.ktx.server.WebServerListener +import com.github.alice.ktx.fsm.FSMContext +import com.github.alice.ktx.fsm.MutableFSMContext +import com.github.alice.ktx.fsm.impl.BaseFSMContext +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment import com.github.alice.ktx.storage.Storage import com.github.alice.ktx.storage.apiStorage.EnableApiStorage import com.github.alice.ktx.storage.impl.memoryStorage @@ -24,7 +24,8 @@ import kotlinx.serialization.json.Json * Эта функция будет вызвана в контексте `Skill.Builder`. * @return Настроенный объект `Skill`. */ -fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().build(body) +@AliceDsl +fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().apply(body).build() /** * Класс `Skill` представляет собой навык, который обрабатывает запросы и управляет состоянием. @@ -34,38 +35,46 @@ fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().build(body) */ class Skill internal constructor( private val webServer: WebServer, - private val dispatcher: Dispatcher + private val dispatcher: Dispatcher, + private val dialogApi: DialogApi?, + private val defaultFSMStrategy: FSMStrategy, + private val fsmContext: (message: MessageRequest) -> FSMContext, + private val enableApiStorage: Boolean = false ) { /** * Конструктор `Builder` для создания экземпляра `Skill`. */ + @AliceDsl class Builder { - var id: String? = null - var json: Json = Json { ignoreUnknownKeys = true } - var dialogApi: DialogApi? = null lateinit var webServer: WebServer - var defaultFSMStrategy: FSMStrategy = FSMStrategy.USER - internal var dispatcherConfiguration: Dispatcher.() -> Unit = { } + var skillId: String? = null + var json: Json = Json { + isLenient = true + ignoreUnknownKeys = true + encodeDefaults = true + } + + var dialogApi: DialogApi? = null var storage: Storage = memoryStorage() + var defaultFSMStrategy: FSMStrategy = FSMStrategy.USER var fsmContext: (message: MessageRequest) -> FSMContext = { message -> - BaseFSMContext(storage, defaultFSMStrategy, message, id) + BaseFSMContext(storage, defaultFSMStrategy, message, skillId) } - internal fun build(body: Builder.() -> Unit): Skill { - body() + internal var dispatcherConfiguration: Dispatcher.() -> Unit = { } + fun build(): Skill { return Skill( webServer = webServer, - dispatcher = Dispatcher( - fsmStrategy = defaultFSMStrategy, - dialogApi = dialogApi, - fsmContext = fsmContext, - enableApiStorage = storage.javaClass.isAnnotationPresent(EnableApiStorage::class.java) - ).apply(dispatcherConfiguration) + dialogApi = dialogApi, + defaultFSMStrategy = defaultFSMStrategy, + fsmContext = fsmContext, + enableApiStorage = storage.javaClass.isAnnotationPresent(EnableApiStorage::class.java), + dispatcher = Dispatcher().apply(dispatcherConfiguration) ) } } @@ -83,25 +92,24 @@ class Skill internal constructor( * * @return Реализованный объект `WebServerResponseListener`, который обрабатывает входящие сообщения и ошибки. */ - private fun webServerResponseCallback(): WebServerResponseListener = object : WebServerResponseListener { - override suspend fun messageHandle(model: MessageRequest): MessageResponse? { - val request = dispatcher.request(model) - val eventRequest = request.toEventRequest() + private fun webServerResponseCallback(): WebServerListener = object : WebServerListener { + override suspend fun handleRequest(model: MessageRequest): MessageResponse? { + val requestEnvironment = createRequestEnvironment(model) - runMiddlewares(request, MiddlewareType.OUTER)?.let { return it } + runMiddlewares(requestEnvironment, MiddlewareType.OUTER)?.let { return it } dispatcher.commandHandlers.forEach { handler -> - if (handler.event(eventRequest)) { - runMiddlewares(request, MiddlewareType.INNER)?.let { return it } - return handler.handle(request) + if (handler.shouldHandle(requestEnvironment)) { + runMiddlewares(requestEnvironment, MiddlewareType.INNER)?.let { return it } + return handler.processRequest(requestEnvironment) } } return null } - override suspend fun responseFailure(model: MessageRequest, ex: Exception): MessageResponse? { - val request = dispatcher.request(model) + override suspend fun handleError(model: MessageRequest, exception: Exception): MessageResponse? { + val request = createRequestEnvironment(model) dispatcher.networkErrorHandlers.forEach { errorHandler -> - errorHandler.responseFailure(request, ex)?.let { response -> + errorHandler.responseFailure(request, exception)?.let { response -> return response } } @@ -114,13 +122,29 @@ class Skill internal constructor( * @param type Тип мидлвари, который следует выполнить. * @return `MessageResponse?` — ответ от мидлвари, или `null`, если обработка не завершена. */ - private suspend fun runMiddlewares(request: Request, type: MiddlewareType): MessageResponse? { + private suspend fun runMiddlewares( + request: ProcessRequestEnvironment, + type: MiddlewareType + ): MessageResponse? { dispatcher.middlewares[type]?.forEach { middleware -> - middleware.invoke(request)?.let { response -> + middleware.process(request)?.let { response -> return response } } return null } } + + private suspend fun createRequestEnvironment(message: MessageRequest): ProcessRequestEnvironment { + val context = fsmContext(message) + context.init() + + return object : ProcessRequestEnvironment { + override val message: MessageRequest = message + override val context: MutableFSMContext = context + override val dialogApi: DialogApi? = this@Skill.dialogApi + override val fsmStrategy: FSMStrategy = this@Skill.defaultFSMStrategy + override val enableApiStorage: Boolean = this@Skill.enableApiStorage + } + } } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/Response.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/Response.kt index a06f8a4..225d6d9 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/Response.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/Response.kt @@ -13,7 +13,7 @@ sealed interface Response { * @param T Тип данных, которые были бы возвращены в успешном ответе. * @param message Сообщение об ошибке, объясняющее причину неудачи. */ - data class Failed(val message: String): Response + data class Failed(val message: String, val code: Int): Response /** * Представляет успешный ответ. diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/extensions/ResponseExtensions.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/extensions/ResponseExtensions.kt index a4b1ee9..c91bd95 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/extensions/ResponseExtensions.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/common/extensions/ResponseExtensions.kt @@ -16,5 +16,5 @@ suspend inline fun HttpResponse.response(): Response { return if(status.value in 200..299) Response.Success(this.body()) else - Response.Failed(this.body().message) + Response.Failed(this.body().message, status.value) } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/impl/KtorYandexDialogApi.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/impl/KtorYandexDialogApi.kt index 2ced355..817d00d 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/impl/KtorYandexDialogApi.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/impl/KtorYandexDialogApi.kt @@ -11,6 +11,7 @@ import com.github.alice.ktx.api.dialog.yandex.models.image.response.Images import com.github.alice.ktx.api.dialog.yandex.models.sounds.response.SoundUpload import com.github.alice.ktx.api.dialog.yandex.models.sounds.response.Sounds import com.github.alice.ktx.api.dialog.yandex.models.status.Status +import com.github.alice.ktx.common.AliceDsl import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* @@ -24,10 +25,11 @@ import io.ktor.util.* import kotlinx.serialization.json.Json import java.io.File +@AliceDsl fun Skill.Builder.ktorYandexDialogApi(body: KtorYandexDialogApi.Builder.() -> Unit): KtorYandexDialogApi { - val id = id + val id = skillId ?: throw IllegalArgumentException("Skill ID не может быть null. Убедитесь, что ID установлен перед вызовом метода.") - return KtorYandexDialogApi.Builder().setJson(json).apply(body).build(id) + return KtorYandexDialogApi.Builder().json(json).apply(body).build(id) } /** @@ -41,14 +43,15 @@ class KtorYandexDialogApi( private val configuration: HttpClientConfig.() -> Unit ): DialogApi { + @AliceDsl class Builder { lateinit var oauthToken: String + lateinit var json: Json - private lateinit var json: Json var configuration: HttpClientConfig.() -> Unit = {} - internal fun setJson(json: Json): Builder { + internal fun json(json: Json): Builder { this.json = json return this } diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/image/Image.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/image/Image.kt index 21e023b..8ee3fed 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/image/Image.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/image/Image.kt @@ -1,6 +1,6 @@ package com.github.alice.ktx.api.dialog.yandex.models.image -import com.github.alice.ktx.common.LocalDateTimeSerializer +import com.github.alice.ktx.common.serializers.LocalDateTimeSerializer import kotlinx.serialization.Serializable import java.time.LocalDateTime diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/sounds/Sound.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/sounds/Sound.kt index 2831b70..588f97d 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/sounds/Sound.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/api/dialog/yandex/models/sounds/Sound.kt @@ -1,6 +1,6 @@ package com.github.alice.ktx.api.dialog.yandex.models.sounds -import com.github.alice.ktx.common.LocalDateTimeSerializer +import com.github.alice.ktx.common.serializers.LocalDateTimeSerializer import kotlinx.serialization.Serializable import java.time.LocalDateTime diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Annotations.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Annotations.kt new file mode 100644 index 0000000..6d64230 --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Annotations.kt @@ -0,0 +1,9 @@ +package com.github.alice.ktx.common + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class AliceDsl + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class AliceResponseDsl diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Redis.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Redis.kt new file mode 100644 index 0000000..59760ed --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Redis.kt @@ -0,0 +1,57 @@ +package com.github.alice.ktx.common + +import io.lettuce.core.RedisClient +import io.lettuce.core.api.StatefulRedisConnection + +/** + * Формирует строку URI для подключения к Redis на основе заданных параметров. + * + * @param host Хост Redis-сервера. По умолчанию "localhost". + * @param port Порт Redis-сервера. По умолчанию 6379. + * @param username Необязательное имя пользователя для аутентификации. Если null, имя пользователя не указывается. + * @param password Необязательный пароль для аутентификации. Если null, пароль не указывается. + * @return Строка URI для подключения к Redis. + * + * Примеры: + * - Если указаны только `host` и `port`: "redis://localhost:6379" + * - Если указан `password` без `username`: "redis://:password@localhost:6379" + * - Если указаны и `username`, и `password`: "redis://username:password@localhost:6379" + */ +fun redisUri( + host: String = "localhost", + port: Int = 6379, + username: String? = null, + password: String? = null +): String { + return if (password != null) { + if (username != null) { + "redis://$username:$password@$host:$port" + } else { + "redis://:$password@$host:$port" + } + } else { + "redis://$host:$port" + } +} + +/** + * Устанавливает соединение с Redis-сервером, используя параметры подключения. + * + * Этот метод создает подключение к Redis с использованием клиента, полученного + * из URI, сформированного на основе переданных параметров. + * + * @param host Хост Redis-сервера. По умолчанию "localhost". + * @param port Порт Redis-сервера. По умолчанию 6379. + * @param username Необязательное имя пользователя для аутентификации. Если null, имя пользователя не указывается. + * @param password Необязательный пароль для аутентификации. Если null, пароль не указывается. + * @return Объект StatefulRedisConnection, представляющий подключение к Redis. + */ +fun connectToRedis( + host: String = "localhost", + port: Int = 6379, + username: String? = null, + password: String? = null +): StatefulRedisConnection { + val redisClient = RedisClient.create(redisUri(host, port, username, password)) + return redisClient.connect() +} \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/LocalDateTimeSerializer.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/serializers/LocalDateTimeSerializer.kt similarity index 95% rename from alice-ktx/src/main/kotlin/com/github/alice/ktx/common/LocalDateTimeSerializer.kt rename to alice-ktx/src/main/kotlin/com/github/alice/ktx/common/serializers/LocalDateTimeSerializer.kt index 3dc37eb..f20baf9 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/LocalDateTimeSerializer.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/common/serializers/LocalDateTimeSerializer.kt @@ -1,4 +1,4 @@ -package com.github.alice.ktx.common +package com.github.alice.ktx.common.serializers import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/FSMContext.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/FSMContext.kt similarity index 89% rename from alice-ktx/src/main/kotlin/com/github/alice/ktx/context/FSMContext.kt rename to alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/FSMContext.kt index 5dafe6e..ec115cb 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/FSMContext.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/FSMContext.kt @@ -1,4 +1,4 @@ -package com.github.alice.ktx.context +package com.github.alice.ktx.fsm /** * Интерфейс для доступа к информации состояния конкретного пользователя. diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/MutableFSMContext.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/MutableFSMContext.kt similarity index 98% rename from alice-ktx/src/main/kotlin/com/github/alice/ktx/context/MutableFSMContext.kt rename to alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/MutableFSMContext.kt index 6f52089..bea4b4a 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/MutableFSMContext.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/MutableFSMContext.kt @@ -1,4 +1,4 @@ -package com.github.alice.ktx.context +package com.github.alice.ktx.fsm import com.github.alice.ktx.models.FSMStrategy import kotlin.reflect.KClass diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/ReadOnlyFSMContext.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/ReadOnlyFSMContext.kt similarity index 96% rename from alice-ktx/src/main/kotlin/com/github/alice/ktx/context/ReadOnlyFSMContext.kt rename to alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/ReadOnlyFSMContext.kt index d8a2333..70a3cbe 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/ReadOnlyFSMContext.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/ReadOnlyFSMContext.kt @@ -1,4 +1,4 @@ -package com.github.alice.ktx.context +package com.github.alice.ktx.fsm import com.github.alice.ktx.models.FSMStrategy import kotlin.reflect.KClass diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/impl/BaseFSMContext.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/impl/BaseFSMContext.kt similarity index 98% rename from alice-ktx/src/main/kotlin/com/github/alice/ktx/context/impl/BaseFSMContext.kt rename to alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/impl/BaseFSMContext.kt index 357a197..9c57618 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/context/impl/BaseFSMContext.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/fsm/impl/BaseFSMContext.kt @@ -1,8 +1,8 @@ -package com.github.alice.ktx.context.impl +package com.github.alice.ktx.fsm.impl import com.github.alice.ktx.models.FSMStrategy import com.github.alice.ktx.models.request.MessageRequest -import com.github.alice.ktx.context.FSMContext +import com.github.alice.ktx.fsm.FSMContext import com.github.alice.ktx.storage.Storage import com.github.alice.ktx.storage.apiStorage.ApiStorageDetails import com.github.alice.ktx.storage.models.StorageKey diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/Handler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/Handler.kt index 96b4780..415cd53 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/Handler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/Handler.kt @@ -1,7 +1,7 @@ package com.github.alice.ktx.handlers -import com.github.alice.ktx.models.EventRequest -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse /** @@ -14,10 +14,10 @@ interface Handler { /** * Определяет, сработает ли обработчик для данного сообщения. * - * @param message Сообщение, которое проверяется на соответствие условиям обработчика. + * @param request Запрос, который проверяется на соответствие условиям обработчика. * @return `true`, если обработчик должен сработать для данного сообщения; `false` в противном случае. */ - suspend fun event(request: EventRequest): Boolean + suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean /** * Выполняет обработку запроса сообщения и возвращает ответ. @@ -25,5 +25,5 @@ interface Handler { * @param request Запрос сообщения, который будет обработан. * @return Ответ на запрос в виде `MessageResponse`. */ - suspend fun handle(request: Request): MessageResponse + suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ProcessRequestEnvironment.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ProcessRequestEnvironment.kt new file mode 100644 index 0000000..155ea50 --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ProcessRequestEnvironment.kt @@ -0,0 +1,14 @@ +package com.github.alice.ktx.handlers.environments + +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.fsm.MutableFSMContext +import com.github.alice.ktx.models.FSMStrategy + +@AliceDsl +interface ProcessRequestEnvironment : ShouldRequestEnvironment { + + override val context: MutableFSMContext + + val fsmStrategy: FSMStrategy + val enableApiStorage: Boolean +} \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ShouldRequestEnvironment.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ShouldRequestEnvironment.kt new file mode 100644 index 0000000..d61c016 --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/environments/ShouldRequestEnvironment.kt @@ -0,0 +1,18 @@ +package com.github.alice.ktx.handlers.environments + +import com.github.alice.ktx.api.dialog.DialogApi +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.fsm.ReadOnlyFSMContext +import com.github.alice.ktx.handlers.filters.Filter +import com.github.alice.ktx.models.request.MessageRequest + +@AliceDsl +interface ShouldRequestEnvironment { + val message: MessageRequest + val context: ReadOnlyFSMContext + val dialogApi: DialogApi? + + fun filter(filter: Filter): Boolean { + return filter.checkFor(this) + } +} \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/error/NetworkErrorHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/error/NetworkErrorHandler.kt index 1020a6c..60d58ba 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/error/NetworkErrorHandler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/error/NetworkErrorHandler.kt @@ -1,23 +1,26 @@ package com.github.alice.ktx.handlers.error import com.github.alice.ktx.Dispatcher -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse import kotlin.reflect.KClass /** * Расширение для `Dispatcher`, добавляющее обработку сетевых ошибок с помощью заданных функций. * - * @param event Функция, которая вызывается при возникновении ошибки. Принимает `Exception` и возвращает `Boolean`. + * @param shouldHandle Функция, которая вызывается при возникновении ошибки. Принимает `Exception` и возвращает `Boolean`. * Если функция возвращает `true`, вызывается обработчик `handle`. - * @param handle Функция-обработчик, которая вызывается, если `event` возвращает `true`. Принимает `Exception` и возвращает `MessageResponse?`. + * @param processRequest Функция-обработчик, которая вызывается, если `event` возвращает `true`. Принимает `Exception` и возвращает `MessageResponse?`. */ +@AliceDsl fun Dispatcher.responseFailure( - event: suspend Request.(ex: Exception) -> Boolean, - handle: suspend Request.(ex: Exception) -> MessageResponse?, + shouldHandle: suspend ShouldRequestEnvironment.(exception: Exception) -> Boolean, + processRequest: suspend ProcessRequestEnvironment.(exception: Exception) -> MessageResponse?, ) { networkError { request, ex -> - if(request.event(ex)) return@networkError request.handle(ex) + if(request.shouldHandle(ex)) return@networkError request.processRequest(ex) null } } @@ -28,7 +31,11 @@ fun Dispatcher.responseFailure( * @param thClass Класс исключения, которое должно быть обработано. * @param block Функция-обработчик, которая вызывается, если исключение принадлежит классу `thClass`. Принимает исключение типа `E` и возвращает `MessageResponse?`. */ -fun Dispatcher.responseFailure(thClass: KClass, block: suspend Request.(ex: E) -> MessageResponse?) { +@AliceDsl +fun Dispatcher.responseFailure( + thClass: KClass, + block: suspend ProcessRequestEnvironment.(exception: E) -> MessageResponse? +) { networkError { request, ex -> if (thClass.isInstance(ex)) { @Suppress("UNCHECKED_CAST") @@ -43,7 +50,8 @@ fun Dispatcher.responseFailure(thClass: KClass, block: suspen * * @param block Функция-обработчик, которая вызывается при возникновении ошибки. Принимает `Exception` и возвращает `MessageResponse?`. */ -fun Dispatcher.responseFailure(block: suspend Request.(ex: Exception) -> MessageResponse?) { +@AliceDsl +fun Dispatcher.responseFailure(block: suspend ProcessRequestEnvironment.(exception: Exception) -> MessageResponse?) { networkError { request, ex -> block(request, ex) } } @@ -53,12 +61,13 @@ fun Dispatcher.responseFailure(block: suspend Request.(ex: Exception) -> Message * @param responseFailure Функция-обработчик, которая вызывается при возникновении ошибки. Принимает модель запроса и исключение, возвращает `MessageResponse?`. * По умолчанию, возвращает `null`, что позволяет передать событие следующему обработчику. */ +@AliceDsl fun Dispatcher.networkError( - responseFailure: suspend (model: Request, ex: Exception) -> MessageResponse? = { _, _ -> null } + responseFailure: suspend (model: ProcessRequestEnvironment, exception: Exception) -> MessageResponse? = { _, _ -> null } ) { val networkErrorHandler = object : NetworkErrorHandler { - override suspend fun responseFailure(request: Request, ex: Exception): MessageResponse? { - return responseFailure(request, ex) + override suspend fun responseFailure(request: ProcessRequestEnvironment, exception: Exception): MessageResponse? { + return responseFailure(request, exception) } } networkError(networkErrorHandler) @@ -80,11 +89,11 @@ interface NetworkErrorHandler { /** * Вызывается при возникновении ошибки * @param request Запроса, при обработке которого произошла ошибка. - * @param ex Исключение, которое возникло. + * @param exception Исключение, которое возникло. * * [responseFailure] должен всегда возвращать null чтобы передать событие следующему хэндлеру. * Если вы хотите завершить обработку события, вы должны вернуть [MessageResponse] * * */ - suspend fun responseFailure(request: Request, ex: Exception): MessageResponse? = null + suspend fun responseFailure(request: ProcessRequestEnvironment, exception: Exception): MessageResponse? = null } diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/filters/Filter.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/filters/Filter.kt new file mode 100644 index 0000000..ac0db40 --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/filters/Filter.kt @@ -0,0 +1,75 @@ +package com.github.alice.ktx.handlers.filters + +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment +import com.github.alice.ktx.models.request.RequestContentType + +interface Filter { + fun checkFor(request: ShouldRequestEnvironment): Boolean = request.predicate() + fun ShouldRequestEnvironment.predicate(): Boolean + + infix fun and(otherFilter: Filter): Filter = object : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = + this@Filter.checkFor(this) && otherFilter.checkFor(this) + } + + infix fun or(otherFilter: Filter): Filter = object : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = + this@Filter.checkFor(this) || otherFilter.checkFor(this) + } + + operator fun not(): Filter = object : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = !this@Filter.checkFor(this) + } + + class Custom(private val customPredicate: ShouldRequestEnvironment.() -> Boolean) : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = customPredicate() + } + + object All : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = true + } + + class Text(private val text: String? = null): Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean { + return text?.let { + message.request.originalUtterance?.contains(it, ignoreCase = true) == true + } ?: (message.request.originalUtterance != null) + } + } + + class Type(private val type: RequestContentType) : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.request.type == type + } + + /** + * Пользователь может видеть ответ навыка на экране и открывать ссылки в браузере. + * */ + object Screen : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.meta.interfaces.screen != null + } + + /** + * У пользователя есть возможность запросить [связку аккаунтов](https://yandex.ru/dev/dialogs/alice/doc/ru/auth/when-to-use). + * */ + object AccountLinking : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.meta.interfaces.accountLinking != null + } + + /** + * На устройстве пользователя есть аудиоплеер. + * */ + object AudioPlayer : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.meta.interfaces.audioPlayer != null + } + + /** + * Признак новой сессии. + * */ + object NewSession : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.session.new + } + + object Authorized : Filter { + override fun ShouldRequestEnvironment.predicate(): Boolean = message.session.user?.userId != null + } +} diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/ButtonPressedHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/ButtonPressedHandler.kt index 2fb8953..ec7f088 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/ButtonPressedHandler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/ButtonPressedHandler.kt @@ -1,40 +1,58 @@ package com.github.alice.ktx.handlers.impl import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.handlers.Handler -import com.github.alice.ktx.models.EventRequest -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.request.RequestContentType import com.github.alice.ktx.models.response.MessageResponse -import com.github.alice.ktx.context.ReadOnlyFSMContext -data class EventButtonPressed( - val context: ReadOnlyFSMContext, - val payload: Map -) +@AliceDsl +data class ButtonPressedShouldHandleEnvironment( + private val requestEnvironment: ShouldRequestEnvironment +): ShouldRequestEnvironment by requestEnvironment { + val payload: Map = requestEnvironment.message.request.payload +} + +/** + * Функция расширения для `Dispatcher`, которая добавляет обработчик событий для нажатия кнопки. + * + * Навык получает запрос с типом ButtonPressed, если в предыдущем ответе пользователь нажал: + * + * 1. отдельную кнопку (свойство hide со значением true) с непустым полем payload; + * 2. изображение (тип BigImage) с непустым полем payload в card.button; + * 3. элемент списка (тип ItemList) с непустым полем payload в items.button; + * 4. изображение из галереи (тип ImageGallery) с непустым полем payload в items.button. + * + * @param shouldHandle Логика, определяющая, должен ли обработчик сработать для данного события. + * @param processRequest Логика обработки запроса, если событие было подтверждено. + */ +@AliceDsl fun Dispatcher.buttonPressed( - event: suspend EventButtonPressed.() -> Boolean = { true }, - handle: suspend Request.() -> MessageResponse + shouldHandle: suspend ButtonPressedShouldHandleEnvironment.() -> Boolean = { true }, + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse ) { addHandler( ButtonPressedHandler( - eventBlock = event, - handleBlock = handle + shouldHandleBlock = shouldHandle, + processRequestBlock = processRequest ) ) } internal class ButtonPressedHandler( - private val eventBlock: suspend EventButtonPressed.() -> Boolean, - private val handleBlock: suspend Request.() -> MessageResponse + private val shouldHandleBlock: suspend ButtonPressedShouldHandleEnvironment.() -> Boolean, + private val processRequestBlock: suspend ProcessRequestEnvironment.() -> MessageResponse ) : Handler { - override suspend fun event(request: EventRequest): Boolean { + + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean { return request.message.request.type == RequestContentType.ButtonPressed && - eventBlock(EventButtonPressed(context = request.context, payload = request.message.request.payload!!)) + shouldHandleBlock(ButtonPressedShouldHandleEnvironment(request)) } - override suspend fun handle(request: Request): MessageResponse { - return handleBlock(request) + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse { + return processRequestBlock(request) } } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/HelpHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/HelpHandler.kt index 81c96de..d68ca93 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/HelpHandler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/HelpHandler.kt @@ -1,24 +1,46 @@ package com.github.alice.ktx.handlers.impl import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.handlers.Handler -import com.github.alice.ktx.models.EventRequest -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse +/** + * Функция расширения для `Dispatcher`, которая добавляет обработчик для запроса помощи. + * Этот обработчик срабатывает, когда пользователь запрашивает помощь. + * + * @param processRequest Логика обработки запроса, которая будет выполнена, когда сработает обработчик. + */ +@AliceDsl fun Dispatcher.help( - handle: suspend Request.() -> MessageResponse + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse ) { - addHandler(HelpHandler(handleBlock = handle)) + addHandler(HelpHandler(processRequestBlock = processRequest)) } internal class HelpHandler( - private val handleBlock: suspend Request.() -> MessageResponse + private val processRequestBlock: suspend ProcessRequestEnvironment.() -> MessageResponse ) : Handler { - override suspend fun event(request: EventRequest): Boolean { - return request.message.request.command == "помощь" || request.message.request.command == "что ты умеешь" + /** + * Определяет, должен ли обработчик сработать для данного запроса. + * Обработчик срабатывает, если команда запроса соответствует "помощь". + * + * @param request Запрос, который проверяется. + * @return `true`, если команда запроса соответствует "помощь"; + * `false` в противном случае. + */ + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean { + return request.message.request.command == "помощь" } - override suspend fun handle(request: Request): MessageResponse = handleBlock(request) + /** + * Выполняет обработку запроса и возвращает соответствующий ответ. + * + * @param request Запрос, который будет обработан. + * @return Ответ на запрос в виде `MessageResponse`. + */ + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse = processRequestBlock(request) } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/MessageHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/MessageHandler.kt index aaaf10d..a9e5353 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/MessageHandler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/MessageHandler.kt @@ -1,43 +1,83 @@ package com.github.alice.ktx.handlers.impl import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.handlers.Handler -import com.github.alice.ktx.models.EventRequest -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse /** - * Расширение для `Dispatcher`, позволяющее установить обработчик сообщений. + * Окружение для проверки, следует ли обрабатывать сообщение. + */ +@AliceDsl +data class MessageShouldHandleEnvironment( + private val request: ShouldRequestEnvironment +) : ShouldRequestEnvironment by request { + + val command = request.message.request.command ?: "" + val messageText = request.message.request.originalUtterance ?: "" +} + +/** + * Окружение для обработки запроса сообщения. + */ +@AliceDsl +data class MessageProcessRequestEnvironment( + private val request: ProcessRequestEnvironment +) : ProcessRequestEnvironment by request { + + val command = request.message.request.command ?: "" + val messageText = request.message.request.originalUtterance ?: "" +} + +/** + * Функция расширения для `Dispatcher`, которая добавляет обработчик для сообщений. + * Этот обработчик срабатывает, если: * - * @param event Функция-условие, определяющая, должен ли обработчик сработать для данного события. Принимает объект `EventMessage` и возвращает `Boolean`. - * @param handle Функция-обработчик, которая будет вызвана, если условие события возвращает `true`. Принимает объект `Request` и возвращает `MessageResponse`. + * 1. Пользователь произносит фразу. + * 2. Пользователь нажимает кнопку в бабле из предыдущего ответа навыка (свойство hide со значением false). + * 3. Пользователь нажимает отдельную кнопку в предыдущем ответе навыка (свойство hide со значением true) с отсутствующим значением в поле payload. + * + * @param shouldHandle Логика, которая определяет, следует ли обрабатывать это сообщение. + * @param processRequest Логика обработки сообщения, которая будет выполнена при удовлетворении условиям. */ +@AliceDsl fun Dispatcher.message( - event: suspend EventRequest.() -> Boolean = { true }, - handle: suspend Request.() -> MessageResponse + shouldHandle: suspend MessageShouldHandleEnvironment.() -> Boolean = { true }, + processRequest: suspend MessageProcessRequestEnvironment.() -> MessageResponse ) { addHandler( MessageHandler( - eventBlock = event, - handleBlock = handle + shouldHandleBlock = shouldHandle, + processRequestBlock = processRequest ) ) } -/** - * Внутренний класс `MessageHandler`, реализующий интерфейс `Handler`. - * - * @param eventBlock Функция, определяющая условие срабатывания обработчика. Принимает объект `MessageRequest` и возвращает `Boolean`. - * @param handleBlock Функция-обработчик запроса. Принимает объект `MessageRequest` и возвращает `MessageResponse`. - */ internal class MessageHandler( - private val eventBlock: suspend EventRequest.() -> Boolean, - private val handleBlock: suspend Request.() -> MessageResponse + private val shouldHandleBlock: suspend MessageShouldHandleEnvironment.() -> Boolean, + private val processRequestBlock: suspend MessageProcessRequestEnvironment.() -> MessageResponse ) : Handler { - override suspend fun event(request: EventRequest): Boolean = eventBlock(request) - - override suspend fun handle(request: Request): MessageResponse { - return handleBlock(request) + /** + * Определяет, следует ли обрабатывать данный запрос. + * + * @param request Запрос, который проверяется. + * @return `true`, если обработчик должен сработать, в противном случае `false`. + */ + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean { + return request.message.request.command != null && + request.message.request.originalUtterance != null && + shouldHandleBlock(MessageShouldHandleEnvironment(request)) } + + /** + * Выполняет обработку запроса и возвращает соответствующий ответ. + * + * @param request Запрос, который будет обработан. + * @return Ответ на запрос в виде `MessageResponse`. + */ + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse = + processRequestBlock(MessageProcessRequestEnvironment(request)) } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/NewSessionHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/NewSessionHandler.kt index 1c2ca29..d2baf4f 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/NewSessionHandler.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/NewSessionHandler.kt @@ -1,33 +1,55 @@ package com.github.alice.ktx.handlers.impl import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl import com.github.alice.ktx.handlers.Handler -import com.github.alice.ktx.models.EventRequest -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse +/** + * Функция расширения для `Dispatcher`, которая добавляет обработчик для нового сеанса. + * Этот обработчик срабатывает, когда создается новый сеанс, и выполняет соответствующую логику. + * + * @param shouldHandle Логика, которая определяет, должен ли обработчик сработать для данного запроса. + * @param processRequest Логика обработки запроса, которая будет выполнена, когда сработает обработчик. + */ +@AliceDsl fun Dispatcher.newSession( - event: suspend EventRequest.() -> Boolean = { true }, - handle: suspend Request.() -> MessageResponse + shouldHandle: suspend ShouldRequestEnvironment.() -> Boolean = { true }, + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse ) { addHandler( NewSessionHandler( - eventBlock = event, - handleBlock = handle + shouldHandleBlock = shouldHandle, + processRequestBlock = processRequest ) ) } internal class NewSessionHandler( - private val eventBlock: suspend EventRequest.() -> Boolean, - private val handleBlock: suspend Request.() -> MessageResponse + private val shouldHandleBlock: suspend ShouldRequestEnvironment.() -> Boolean, + private val processRequestBlock: suspend ProcessRequestEnvironment.() -> MessageResponse ) : Handler { - override suspend fun event(request: EventRequest): Boolean { - return request.message.session.new && eventBlock(request) + /** + * Определяет, сработает ли обработчик для данного запроса. + * Обработчик срабатывает, если запрос связан с новым сеансом. + * + * @param request Запрос, который проверяется. + * @return `true`, если запрос относится к новому сеансу и условие `shouldHandle` выполняется. + */ + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean { + return request.message.session.new && shouldHandleBlock(request) } - override suspend fun handle(request: Request): MessageResponse { - return handleBlock(request) + /** + * Выполняет обработку запроса и возвращает соответствующий ответ. + * + * @param request Запрос, который будет обработан. + * @return Ответ на запрос в виде `MessageResponse`. + */ + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse { + return processRequestBlock(request) } } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/RequestHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/RequestHandler.kt new file mode 100644 index 0000000..647a09d --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/RequestHandler.kt @@ -0,0 +1,63 @@ +package com.github.alice.ktx.handlers.impl + +import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.handlers.Handler +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment +import com.github.alice.ktx.handlers.filters.Filter +import com.github.alice.ktx.models.response.MessageResponse + +@AliceDsl +fun Dispatcher.request( + filler: Filter, + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse +) { + request( + shouldHandle = { filler.checkFor(this) }, + processRequest = processRequest + ) +} + +/** + * Регистрация обработчика для всех типов запросов. + * Этот метод позволяет настроить обработчик для любых запросов, + * независимо от их типа. + * + * @param shouldHandle Логика, определяющая, должен ли обработчик сработать для данного запроса. + * @param processRequest Логика обработки запроса, которая выполняется, если запрос должен быть обработан. + */ +@AliceDsl +fun Dispatcher.request( + shouldHandle: suspend ShouldRequestEnvironment.() -> Boolean = { true }, + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse +) { + addHandler( + RequestHandler( + shouldHandleBlock = shouldHandle, + processRequestBlock = processRequest + ) + ) +} + +internal class RequestHandler( + private val shouldHandleBlock: suspend ShouldRequestEnvironment.() -> Boolean, + private val processRequestBlock: suspend ProcessRequestEnvironment.() -> MessageResponse +) : Handler { + + /** + * Метод, который проверяет, должен ли обработчик сработать для данного запроса. + * + * @param request Запрос, который будет проверен. + * @return true, если обработчик должен сработать, иначе false. + */ + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean = shouldHandleBlock(request) + + /** + * Метод, который выполняет обработку запроса и возвращает ответ. + * + * @param request Запрос, который нужно обработать. + * @return Ответ на запрос в виде MessageResponse. + */ + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse = processRequestBlock(request) +} \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/WhatCanYouDoHandler.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/WhatCanYouDoHandler.kt new file mode 100644 index 0000000..946da71 --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/handlers/impl/WhatCanYouDoHandler.kt @@ -0,0 +1,46 @@ +package com.github.alice.ktx.handlers.impl + +import com.github.alice.ktx.Dispatcher +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.handlers.Handler +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment +import com.github.alice.ktx.handlers.environments.ShouldRequestEnvironment +import com.github.alice.ktx.models.response.MessageResponse + +/** + * Функция расширения для `Dispatcher`, которая добавляет обработчик для запроса "что ты умеешь". + * Этот обработчик срабатывает, когда пользователь запрашивает информацию о возможностях системы. + * + * @param processRequest Логика обработки запроса, которая будет выполнена, когда сработает обработчик. + */ +@AliceDsl +fun Dispatcher.whatCanYouDo( + processRequest: suspend ProcessRequestEnvironment.() -> MessageResponse +) { + addHandler(WhatCanYouDoHandler(processRequestBlock = processRequest)) +} + +internal class WhatCanYouDoHandler( + private val processRequestBlock: suspend ProcessRequestEnvironment.() -> MessageResponse +) : Handler { + + /** + * Определяет, должен ли обработчик сработать для данного запроса. + * Обработчик срабатывает, если команда запроса соответствует "что ты умеешь". + * + * @param request Запрос, который проверяется. + * @return `true`, если команда запроса соответствует "что ты умеешь"; + * `false` в противном случае. + */ + override suspend fun shouldHandle(request: ShouldRequestEnvironment): Boolean { + return request.message.request.command == "что ты умеешь" + } + + /** + * Выполняет обработку запроса и возвращает соответствующий ответ. + * + * @param request Запрос, который будет обработан. + * @return Ответ на запрос в виде `MessageResponse`. + */ + override suspend fun processRequest(request: ProcessRequestEnvironment): MessageResponse = processRequestBlock(request) +} \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/Middleware.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/Middleware.kt index c081246..9568696 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/Middleware.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/Middleware.kt @@ -1,43 +1,46 @@ package com.github.alice.ktx.middleware import com.github.alice.ktx.Dispatcher -import com.github.alice.ktx.models.Request +import com.github.alice.ktx.common.AliceDsl +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment import com.github.alice.ktx.models.response.MessageResponse /** * Расширение для `Dispatcher`, добавляющее внешний мидлварь (outer middleware), который будет вызван при каждом входящем событии. * - * @param invoke Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `Request` и возвращает `MessageResponse?`. + * @param process Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `Request` и возвращает `MessageResponse?`. * Мидлварь должен возвращать `null`, чтобы передать событие следующему мидлварю/хэндлеру. * Если требуется завершить обработку события, необходимо вернуть `MessageResponse`. */ -fun Dispatcher.outerMiddleware(invoke: suspend Request.() -> MessageResponse?) { - val middleware = middleware { request -> invoke(request) } +@AliceDsl +fun Dispatcher.outerMiddleware(process: suspend ProcessRequestEnvironment.() -> MessageResponse?) { + val middleware = middleware { request -> process(request) } addMiddleware(middleware, MiddlewareType.OUTER) } /** * Расширение для `Dispatcher`, добавляющее внутренний мидлварь (inner middleware), который будет вызван только при прохождении фильтров. * - * @param invoke Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `Request` и возвращает `MessageResponse?`. + * @param process Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `Request` и возвращает `MessageResponse?`. * Мидлварь должен возвращать `null`, чтобы передать событие следующему мидлварю/хэндлеру. * Если требуется завершить обработку события, необходимо вернуть `MessageResponse`. */ -fun Dispatcher.innerMiddleware(invoke: suspend Request.() -> MessageResponse?) { - val middleware = middleware { request -> invoke(request) } +@AliceDsl +fun Dispatcher.innerMiddleware(process: suspend ProcessRequestEnvironment.() -> MessageResponse?) { + val middleware = middleware { request -> process(request) } addMiddleware(middleware, MiddlewareType.INNER) } /** * Создает мидлварь с заданной функцией обработки. * - * @param invoke Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `MessageRequest` и возвращает `MessageResponse?`. + * @param process Функция, которая будет вызвана для обработки запроса сообщения. Принимает объект `MessageRequest` и возвращает `MessageResponse?`. * Мидлварь должен возвращать `null`, чтобы передать событие следующему мидлварю/хэндлеру. * Если требуется завершить обработку события, необходимо вернуть `MessageResponse`. * @return Реализованный объект `Middleware`. */ -fun middleware(invoke: suspend (Request) -> MessageResponse?): Middleware = object : Middleware { - override suspend fun invoke(request: Request): MessageResponse? = invoke(request) +fun middleware(process: suspend (ProcessRequestEnvironment) -> MessageResponse?): Middleware = object : Middleware { + override suspend fun process(request: ProcessRequestEnvironment): MessageResponse? = process(request) } /** @@ -49,5 +52,5 @@ interface Middleware { * Если вы хотите завершить обработку события, вы должны вернуть [MessageResponse] * * */ - suspend fun invoke(request: Request): MessageResponse? + suspend fun process(request: ProcessRequestEnvironment): MessageResponse? } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/MiddlewareType.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/MiddlewareType.kt index 0cb629d..3e439af 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/MiddlewareType.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/middleware/MiddlewareType.kt @@ -2,7 +2,7 @@ package com.github.alice.ktx.middleware /** * [OUTER] будут вызываться при каждом входящем событиию - * [INNER] удут вызываться только при прохождении фильтров + * [INNER] будут вызываться только при прохождении фильтров * */ enum class MiddlewareType { OUTER, diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/EventRequest.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/EventRequest.kt deleted file mode 100644 index 88d3b50..0000000 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/EventRequest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.alice.ktx.models - -import com.github.alice.ktx.models.request.MessageRequest -import com.github.alice.ktx.context.ReadOnlyFSMContext - -internal fun Request.toEventRequest(): EventRequest { - return EventRequest( - message = message, - context = context - ) -} - -data class EventRequest( - val message: MessageRequest, - val context: ReadOnlyFSMContext -) \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/Request.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/Request.kt deleted file mode 100644 index 5dc398c..0000000 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/Request.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.alice.ktx.models - -import com.github.alice.ktx.Dispatcher -import com.github.alice.ktx.models.request.MessageRequest -import com.github.alice.ktx.context.MutableFSMContext - -internal suspend fun Dispatcher.request(message: MessageRequest): Request { - val context = fsmContext(message) - context.init() - - return Request( - message = message, - context = context, - fsmStrategy = fsmStrategy, - enableApiStorage = enableApiStorage - ) -} - -data class Request( - val message: MessageRequest, - val context: MutableFSMContext, - internal val fsmStrategy: FSMStrategy, - internal val enableApiStorage: Boolean -) \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/audioPlayer/AudioPlayer.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/audioPlayer/AudioPlayer.kt index e1fb8f3..0f6563f 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/audioPlayer/AudioPlayer.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/audioPlayer/AudioPlayer.kt @@ -1,9 +1,11 @@ package com.github.alice.ktx.models.audioPlayer +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.response.MessageResponse import kotlinx.serialization.Serializable import java.util.* +@AliceResponseDsl fun MessageResponse.Builder.audioPlayer(body: AudioPlayer.Builder.() -> Unit) { val player = AudioPlayer.Builder().build(body) audioPlayer = player @@ -18,40 +20,41 @@ data class AudioPlayer internal constructor( val action: AudioPlayerAction, val item: AudioPlayerItem ) { - class Builder { - var action = AudioPlayerAction.Play + @AliceResponseDsl + class Builder { + var action = AudioPlayerAction.Play - var url: String? = null - var token = UUID.randomUUID().toString() - var offsetMs = 0 + var url: String? = null + var token = UUID.randomUUID().toString() + var offsetMs = 0 - var title: String? = null - var subTitle: String? = null - var artUrl: String? = null - var backgroundImageUrl: String? = null + var title: String? = null + var subTitle: String? = null + var artUrl: String? = null + var backgroundImageUrl: String? = null - internal fun build(body: Builder.() -> Unit): AudioPlayer { - body() - return AudioPlayer( - action = action, - item = AudioPlayerItem( - stream = AudioPlayerStream( - url = url, - offsetMs = offsetMs, - token = token - ), - metadata = AudioPlayerMetadata( - title = title, - subTitle = subTitle, - art = AudioPlayerMetadataArt( - url = artUrl - ), - backgroundImage = AudioPlayerMetadataBackgroundImage( - url = backgroundImageUrl - ) - ) - ) - ) - } - } + internal fun build(body: Builder.() -> Unit): AudioPlayer { + body() + return AudioPlayer( + action = action, + item = AudioPlayerItem( + stream = AudioPlayerStream( + url = url, + offsetMs = offsetMs, + token = token + ), + metadata = AudioPlayerMetadata( + title = title, + subTitle = subTitle, + art = AudioPlayerMetadataArt( + url = artUrl + ), + backgroundImage = AudioPlayerMetadataBackgroundImage( + url = backgroundImageUrl + ) + ) + ) + ) + } + } } \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/Button.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/Button.kt index 21d3ba9..dac3a93 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/Button.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/Button.kt @@ -1,8 +1,10 @@ package com.github.alice.ktx.models.button +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.response.MessageResponse import kotlinx.serialization.Serializable +@AliceResponseDsl fun MessageResponse.Builder.button(body: Button.Builder.() -> Unit) { val button = Button.Builder().build(body) addButton(button) @@ -15,6 +17,7 @@ data class Button( val url: String?, val hide: Boolean ) { + @AliceResponseDsl class Builder { lateinit var title: String var url: String? = null diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/MediaButton.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/MediaButton.kt index 4b5bf30..2b2092d 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/MediaButton.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/button/MediaButton.kt @@ -1,25 +1,30 @@ package com.github.alice.ktx.models.button +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.card.Card import com.github.alice.ktx.models.card.CardFooter import com.github.alice.ktx.models.card.CardItem import kotlinx.serialization.Serializable +@AliceResponseDsl fun Card.BigImageBuilder.mediaButton(body: MediaButton.Builder.() -> Unit) { val mediaButton = MediaButton.Builder().build(body) button = mediaButton } +@AliceResponseDsl fun CardItem.ImageGalleryBuilder.mediaButton(body: MediaButton.Builder.() -> Unit) { val mediaButton = MediaButton.Builder().build(body) button = mediaButton } +@AliceResponseDsl fun CardItem.ItemsListBuilder.mediaButton(body: MediaButton.Builder.() -> Unit) { val mediaButton = MediaButton.Builder().build(body) button = mediaButton } +@AliceResponseDsl fun CardFooter.Builder.mediaButton(body: MediaButton.Builder.() -> Unit) { val mediaButton = MediaButton.Builder().build(body) button = mediaButton @@ -31,6 +36,7 @@ data class MediaButton( val url: String? = null, val payload: Map? = null ) { + @AliceResponseDsl class Builder { var text: String? = null var url: String? = null diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/Card.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/Card.kt index c73fba2..f0961e2 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/Card.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/Card.kt @@ -1,5 +1,6 @@ package com.github.alice.ktx.models.card +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.button.MediaButton import com.github.alice.ktx.models.response.MessageResponse import kotlinx.serialization.SerialName @@ -8,6 +9,7 @@ import kotlinx.serialization.Serializable /** * [Source](https://yandex.ru/dev/dialogs/alice/doc/ru/response-card-bigimage) * */ +@AliceResponseDsl fun MessageResponse.Builder.cardBigImage(body: Card.BigImageBuilder.() -> Unit) { val card = Card.BigImageBuilder().build(body) this.card = card @@ -16,6 +18,7 @@ fun MessageResponse.Builder.cardBigImage(body: Card.BigImageBuilder.() -> Unit) /** * [Source](https://yandex.ru/dev/dialogs/alice/doc/ru/response-card-imagegallery) * */ +@AliceResponseDsl fun MessageResponse.Builder.cardImageGallery(body: Card.ImageGalleryBuilder.() -> Unit) { val card = Card.ImageGalleryBuilder().build(body) this.card = card @@ -24,6 +27,7 @@ fun MessageResponse.Builder.cardImageGallery(body: Card.ImageGalleryBuilder.() - /** * [Source](https://yandex.ru/dev/dialogs/alice/doc/ru/response-card-itemslist) * */ +@AliceResponseDsl fun MessageResponse.Builder.cardItemsList(body: Card.ItemsListBuilder.() -> Unit) { val card = Card.ItemsListBuilder().build(body) this.card = card @@ -41,6 +45,7 @@ data class Card internal constructor( val items: List? = null, val footer: CardFooter? = null ) { + @AliceResponseDsl class BigImageBuilder { lateinit var imageId: String var title: String? = null @@ -59,6 +64,7 @@ data class Card internal constructor( } } + @AliceResponseDsl class ImageGalleryBuilder { private val items = mutableListOf() @@ -75,6 +81,7 @@ data class Card internal constructor( } } + @AliceResponseDsl class ItemsListBuilder { private val items = mutableListOf() lateinit var header: String diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardFooter.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardFooter.kt index 849ce34..9533070 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardFooter.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardFooter.kt @@ -1,8 +1,10 @@ package com.github.alice.ktx.models.card +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.button.MediaButton import kotlinx.serialization.Serializable +@AliceResponseDsl fun Card.ItemsListBuilder.footer(body: CardFooter.Builder.() -> Unit) { val footer = CardFooter.Builder().build(body) this.footer = footer @@ -13,6 +15,7 @@ data class CardFooter( val text: String, val button: MediaButton? = null ){ + @AliceResponseDsl class Builder { lateinit var text: String var button: MediaButton? = null diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardItem.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardItem.kt index ff66867..67ab14c 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardItem.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/card/CardItem.kt @@ -1,14 +1,17 @@ package com.github.alice.ktx.models.card +import com.github.alice.ktx.common.AliceResponseDsl import com.github.alice.ktx.models.button.MediaButton import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +@AliceResponseDsl fun Card.ImageGalleryBuilder.item(body: CardItem.ImageGalleryBuilder.() -> Unit) { val item = CardItem.ImageGalleryBuilder().build(body) addItem(item) } +@AliceResponseDsl fun Card.ItemsListBuilder.item(body: CardItem.ItemsListBuilder.() -> Unit) { val item = CardItem.ItemsListBuilder().build(body) addItem(item) @@ -22,6 +25,7 @@ data class CardItem( val description: String? = null, val button: MediaButton? = null ) { + @AliceResponseDsl class ImageGalleryBuilder { lateinit var imageId: String var title: String? = null @@ -37,6 +41,7 @@ data class CardItem( } } + @AliceResponseDsl class ItemsListBuilder { lateinit var imageId: String var title: String? = null diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Markup.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Markup.kt new file mode 100644 index 0000000..b95379b --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Markup.kt @@ -0,0 +1,10 @@ +package com.github.alice.ktx.models.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Markup( + @SerialName("dangerous_context") + val dangerousContext: Boolean +) \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/MetadataInterfaces.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/MetadataInterfaces.kt index 7dd7c10..3b4640f 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/MetadataInterfaces.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/MetadataInterfaces.kt @@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable data class MetadataInterfaces( @SerialName("account_linking") val accountLinking: AccountLinking? = null, + @SerialName("audio_player") + val audioPlayer: AudioPlayer? = null, val screen: Screen? = null, val payments: Payments? = null ) @@ -18,4 +20,7 @@ class AccountLinking class Screen @Serializable -class Payments \ No newline at end of file +class Payments + +@Serializable +class AudioPlayer \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Nlu.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Nlu.kt new file mode 100644 index 0000000..435cf6b --- /dev/null +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/Nlu.kt @@ -0,0 +1,9 @@ +package com.github.alice.ktx.models.request + +import kotlinx.serialization.Serializable + +@Serializable +data class Nlu( + val tokens: List = emptyList(), + val intents: Map = emptyMap(), +) \ No newline at end of file diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/RequestContent.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/RequestContent.kt index bef765b..5ecffb0 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/RequestContent.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/request/RequestContent.kt @@ -9,5 +9,8 @@ data class RequestContent( @SerialName("original_utterance") val originalUtterance: String? = null, val type: RequestContentType, - val payload: Map? = null + val payload: Map = emptyMap(), + val markup: Markup? = null, + val nlu: Nlu = Nlu(), + val tokens: Map = emptyMap() ) diff --git a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/response/MessageResponse.kt b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/response/MessageResponse.kt index 909956b..e5d53ec 100644 --- a/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/response/MessageResponse.kt +++ b/alice-ktx/src/main/kotlin/com/github/alice/ktx/models/response/MessageResponse.kt @@ -1,27 +1,32 @@ package com.github.alice.ktx.models.response +import com.github.alice.ktx.common.AliceResponseDsl +import com.github.alice.ktx.fsm.MutableFSMContext +import com.github.alice.ktx.fsm.ReadOnlyFSMContext +import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment import com.github.alice.ktx.models.FSMStrategy -import com.github.alice.ktx.models.Request import com.github.alice.ktx.models.audioPlayer.AudioPlayer import com.github.alice.ktx.models.button.Button import com.github.alice.ktx.models.card.Card import com.github.alice.ktx.models.request.AccountLinking import com.github.alice.ktx.models.response.analytics.Analytics -import com.github.alice.ktx.context.ReadOnlyFSMContext import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -suspend fun Request.response(body: suspend MessageResponse.Builder.() -> Unit): MessageResponse { - return MessageResponse.Builder(this) - .setFSMStrategy(fsmStrategy) - .setEnableApiStorage(enableApiStorage) - .build(body) +@AliceResponseDsl +suspend fun ProcessRequestEnvironment.response(block: MessageResponse.Builder.() -> Unit): MessageResponse { + return MessageResponse.Builder(message.version) + .fsmStrategy(fsmStrategy) + .enableApiStorage(enableApiStorage) + .apply(block) + .build(context) } /** * [Source](https://yandex.ru/dev/dialogs/alice/doc/ru/auth/when-to-use) * */ -suspend fun Request.authorization( +@AliceResponseDsl +suspend fun ProcessRequestEnvironment.authorization( onAlreadyAuthenticated: (suspend () -> MessageResponse)? = null, onAuthorizationFailed: (suspend () -> MessageResponse)? = null ): MessageResponse { @@ -31,7 +36,7 @@ suspend fun Request.authorization( if (message.meta.interfaces.accountLinking == null && onAuthorizationFailed != null) return onAuthorizationFailed() - return MessageResponse.AuthorizationBuilder(request = this).build() + return MessageResponse.AuthorizationBuilder(version = message.version).build() } /** @@ -51,16 +56,18 @@ data class MessageResponse internal constructor( val startAccountLinking: AccountLinking? = null, val analytics: Analytics? = null ) { - class Builder(private val request: Request) { + @AliceResponseDsl + class Builder(private val version: String) { var text: String = "" var tts: String? = null var endSession: Boolean = false var shouldListen: Boolean? = null - var version: String = request.message.version + var analytics: Analytics? = null + internal var card: Card? = null internal var audioPlayer: AudioPlayer? = null + private val buttons = mutableListOf