Skip to content

Commit

Permalink
jaicp-livechat-adapter: improvements
Browse files Browse the repository at this point in the history
- rename gateway to InvocationAPI
- add more parameters to request templates
- add InvocationServlet
- add GET methods for invocation
  • Loading branch information
Denire committed Mar 4, 2021
1 parent 8365e34 commit 82f60f3
Show file tree
Hide file tree
Showing 26 changed files with 395 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.justai.jaicf.channel.facebook

import com.github.messenger4j.exception.MessengerVerificationException
import com.justai.jaicf.api.BotApi
import com.justai.jaicf.channel.facebook.api.FacebookGatewayRequest
import com.justai.jaicf.channel.facebook.api.FacebookInvocationRequest
import com.justai.jaicf.channel.facebook.api.toBotRequest
import com.justai.jaicf.channel.facebook.messenger.Messenger
import com.justai.jaicf.channel.http.HttpBotRequest
Expand All @@ -11,13 +11,14 @@ import com.justai.jaicf.channel.http.asTextHttpBotResponse
import com.justai.jaicf.channel.jaicp.JaicpCompatibleAsyncBotChannel
import com.justai.jaicf.channel.jaicp.JaicpCompatibleAsyncChannelFactory
import com.justai.jaicf.context.RequestContext
import com.justai.jaicf.gateway.BotGateway
import com.justai.jaicf.gateway.BotGatewayRequest
import com.justai.jaicf.channel.invocationapi.InvocableBotChannel
import com.justai.jaicf.channel.invocationapi.InvocationRequest
import java.util.*

class FacebookChannel private constructor(
override val botApi: BotApi
) : JaicpCompatibleAsyncBotChannel, BotGateway() {
) : JaicpCompatibleAsyncBotChannel,
InvocableBotChannel {

private lateinit var messenger: Messenger

Expand Down Expand Up @@ -55,13 +56,13 @@ class FacebookChannel private constructor(
override val channelType = "facebook"
override fun create(botApi: BotApi, apiUrl: String) = FacebookChannel(botApi, apiUrl)

private const val REQUEST_TEMPLATE_PATH = "/FacebookRequestTemplate.json"
internal const val REQUEST_TEMPLATE_PATH = "/FacebookRequestTemplate.json"
}

override fun processGatewayRequest(request: BotGatewayRequest, requestContext: RequestContext) {
override fun processExternalInvocation(request: InvocationRequest, requestContext: RequestContext) {
val template = getRequestTemplateFromResources(request, REQUEST_TEMPLATE_PATH)
messenger.onReceiveEvents(template, Optional.empty()) { event ->
FacebookGatewayRequest.create(request, event.asTextMessageEvent())?.let {
FacebookInvocationRequest.create(request, event.asTextMessageEvent())?.let {
botApi.process(it, FacebookReactions(messenger, it), requestContext)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import com.github.messenger4j.webhook.event.*
import com.justai.jaicf.api.BotRequest
import com.justai.jaicf.api.EventBotRequest
import com.justai.jaicf.api.QueryBotRequest
import com.justai.jaicf.gateway.BotGatewayEventRequest
import com.justai.jaicf.gateway.BotGatewayQueryRequest
import com.justai.jaicf.gateway.BotGatewayRequest
import com.justai.jaicf.channel.invocationapi.InvocationEventRequest
import com.justai.jaicf.channel.invocationapi.InvocationQueryRequest
import com.justai.jaicf.channel.invocationapi.InvocationRequest

val BotRequest.facebook get() = this as? FacebookBotRequest

Expand Down Expand Up @@ -97,26 +97,26 @@ internal fun Event.toBotRequest(): FacebookBotRequest = when {
else -> FacebookFallbackBotRequest(FallbackEvent(senderId(), recipientId(), timestamp()))
}

interface FacebookGatewayRequest : FacebookBotRequest, BotGatewayRequest {
interface FacebookInvocationRequest : FacebookBotRequest, InvocationRequest {
companion object {
fun create(r: BotGatewayRequest, event: BaseEvent) = when (r) {
is BotGatewayEventRequest -> FacebookGatewayEventRequest(event, r.clientId, r.input, r.requestData)
is BotGatewayQueryRequest -> FacebookGatewayQueryRequest(event, r.clientId, r.input, r.requestData)
fun create(r: InvocationRequest, event: BaseEvent) = when (r) {
is InvocationEventRequest -> FacebookInvocationEventRequest(event, r.clientId, r.input, r.requestData)
is InvocationQueryRequest -> FacebookInvocationQueryRequest(event, r.clientId, r.input, r.requestData)
else -> null
}
}
}

data class FacebookGatewayEventRequest(
data class FacebookInvocationEventRequest(
override val event: BaseEvent,
override val clientId: String,
override val input: String,
override val requestData: String
) : FacebookGatewayRequest, BotGatewayEventRequest(clientId, input, requestData)
) : FacebookInvocationRequest, InvocationEventRequest(clientId, input, requestData)

data class FacebookGatewayQueryRequest(
data class FacebookInvocationQueryRequest(
override val event: BaseEvent,
override val clientId: String,
override val input: String,
override val requestData: String
) : FacebookGatewayRequest, BotGatewayQueryRequest(clientId, input, requestData)
) : FacebookInvocationRequest, InvocationQueryRequest(clientId, input, requestData)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"object": "page",
"entry": [
{
"id": "110387787249256",
"time": 1611153924637,
"id": "{{ randomInt }}",
"time": "{{ timestamp }}",
"messaging": [
{
"sender": {
Expand All @@ -12,9 +12,9 @@
"recipient": {
"id": "{{ clientId }}"
},
"timestamp": 1611153924412,
"timestamp": "{{ timestamp }}",
"message": {
"mid": "predefined_message_id",
"mid": "{{ messageId }}",
"text": "{{ text }}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ package com.justai.jaicf.channel.jaicp
* @property fileEvent - is sent when user sends file or image. File will be uploaded to s3 storage,
* link will be provided in request.telephony?.jaicp?.data
* @property liveChatFinished - is sent when livechat is finished and request execution is returned to bot.
* @property noLivechatOperatorsOnline - is sent when scenario attempted switchToOperator, but no operators were online.
* @property noLivechatOperatorsOnline - is sent when scenario attempted switchToLiveChat, but no operators were online.
*
* @see com.justai.jaicf.channel.jaicp.channels.TelephonyEvents
* @see com.justai.jaicf.channel.jaicp.reactions.switchToOperator
* @see com.justai.jaicf.channel.jaicp.reactions.switchToLiveChat
* */
@Suppress("MemberVisibilityCanBePrivate")
object JaicpEvents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ data class JaicpBotRequest(

fun stringify() = JSON.encodeToString(serializer(), this)

internal fun isGatewayRequest() = rawRequest["commonType"]?.jsonPrimitive?.contentOrNull == "COMMON"
internal fun isExternalInvocationRequest() = rawRequest["commonType"]?.jsonPrimitive?.contentOrNull == "COMMON"

internal fun asHttpBotRequest() = raw.asHttpBotRequest(stringify())
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class TelephonySwitchReply(
* @param sendMessagesToOperator - true to send conversation history to operator.
* @param sendMessageHistoryAmount - amount of last conversation history messages to send to operator.
*
* @see com.justai.jaicf.channel.jaicp.reactions.switchToOperator
* @see com.justai.jaicf.channel.jaicp.reactions.switchToLiveChat
* */
@Serializable
data class LiveChatSwitchReply(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.justai.jaicf.channel.jaicp.execution

import com.justai.jaicf.channel.BotChannel
import com.justai.jaicf.channel.http.asHttpBotRequest
import com.justai.jaicf.channel.jaicp.*
import com.justai.jaicf.channel.jaicp.JSON
import com.justai.jaicf.channel.jaicp.channels.JaicpNativeBotChannel
import com.justai.jaicf.channel.jaicp.dto.JaicpBotRequest
import com.justai.jaicf.channel.jaicp.dto.JaicpBotResponse
import com.justai.jaicf.channel.jaicp.dto.fromRequest
import com.justai.jaicf.channel.jaicp.gateway.BotGatewayRequestAdapter
import com.justai.jaicf.gateway.BotGateway
import com.justai.jaicf.channel.jaicp.invocationapi.InvocationRequestData
import com.justai.jaicf.context.RequestContext
import com.justai.jaicf.channel.invocationapi.InvocationEventRequest
import com.justai.jaicf.channel.invocationapi.InvocableBotChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
Expand Down Expand Up @@ -51,14 +52,13 @@ class ThreadPoolRequestExecutor(nThreads: Int) : CoroutineScope {
channel.processCompatible(request)

private fun executeAsync(channel: JaicpCompatibleAsyncBotChannel, request: JaicpBotRequest) {
if (channel is BotGateway && BotGatewayRequestAdapter.ensureGatewayRequest(channel, request)) {
return
val isProcessed = tryProcessAsExternalInvocation(channel, request)
if (!isProcessed) {
channel.process(request.asHttpBotRequest())
}
channel.process(request.asHttpBotRequest())
}
}


private fun JaicpCompatibleBotChannel.processCompatible(
botRequest: JaicpBotRequest
): JaicpBotResponse {
Expand All @@ -81,3 +81,22 @@ private fun addRawReply(rawResponse: JsonElement) = buildJsonObject {
})
}
}

private fun tryProcessAsExternalInvocation(
channel: JaicpCompatibleAsyncBotChannel,
request: JaicpBotRequest
): Boolean {
if (channel !is InvocableBotChannel) return false
if (!request.isExternalInvocationRequest()) return false
val event = request.event ?: return false
val data = try {
JSON.decodeFromString(InvocationRequestData.serializer(), request.raw)
} catch (e: Exception) {
return false
}
channel.processExternalInvocation(
request = InvocationEventRequest(data.chatId, event, request.raw),
requestContext = RequestContext.fromHttp(request.asHttpBotRequest())
)
return true
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.justai.jaicf.channel.jaicp.gateway
package com.justai.jaicf.channel.jaicp.invocationapi

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

@Serializable
internal data class BotGatewayRequestData(
internal data class InvocationRequestData(
val commonType: String,
val chatId: String,
val timestamp: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
package com.justai.jaicf.channel.jaicp.livechat.exceptions

import com.justai.jaicf.channel.jaicp.dto.JaicpBotRequest
import com.justai.jaicf.channel.jaicp.reactions.switchToOperator
import com.justai.jaicf.channel.jaicp.reactions.switchToLiveChat
import com.justai.jaicf.channel.jaicp.dto.LiveChatSwitchReply
import com.justai.jaicf.reactions.jaicp.JaicpCompatibleAsyncReactions

/**
* An exception thrown by [switchToOperator] reaction when no operators are available to switch conversation to operator.
* An exception thrown by [switchToLiveChat] reaction when no operators are available to switch conversation to operator.
* [LiveChatSwitchReply.ignoreOffline] parameter can be used in switch reply to ignore if operators are offline.
*
* @see [JaicpCompatibleAsyncReactions] base interface for jaicp asynchronous reactions
* @see [LiveChatSwitchReply] full object with livechat switch parameters
* @see [switchToOperator] jaicpAsync reaction
* @see [switchToLiveChat] jaicpAsync reaction
* */
class NoOperatorsOnlineException(request: JaicpBotRequest) : RuntimeException() {
override val message: String = "No operators online for channel ${request.channelBotId}"
}

/**
* An exception thrown by [switchToOperator] reaction when livechat is not configured for current channel.
* An exception thrown by [switchToLiveChat] reaction when livechat is not configured for current channel.
*
* @see [JaicpCompatibleAsyncReactions] base interface for jaicp asynchronous reactions
* @see [switchToOperator] jaicpAsync reaction
* @see [switchToLiveChat] jaicpAsync reaction
* */
class NoOperatorChannelConfiguredException(request: JaicpBotRequest) : RuntimeException() {
override val message: String = "No operator channel configured for channel ${request.channelBotId}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ open class JaicpConversationLogger(

internal open fun createLog(req: JaicpBotRequest, ctx: LoggingContext, session: SessionData) =
JaicpLogModel.fromRequest(req, ctx, session).also {
logger.debug("Send log with sessionId: ${it.sessionId} isNewSession: ${it.isNewSession}")
logger.trace("Send log with sessionId: ${it.sessionId} isNewSession: ${it.isNewSession}")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import com.justai.jaicf.channel.jaicp.http.ChatAdapterConnector
import com.justai.jaicf.channel.jaicp.livechat.LiveChatInitRequest
import com.justai.jaicf.channel.jaicp.livechat.exceptions.NoOperatorChannelConfiguredException
import com.justai.jaicf.channel.jaicp.livechat.exceptions.NoOperatorsOnlineException
import com.justai.jaicf.logging.Reaction
import com.justai.jaicf.reactions.jaicp.JaicpCompatibleAsyncReactions
import kotlinx.serialization.json.JsonObject

/**
* Switches to livechat operator if channel is connected to livechat in JAICP App Console.
Expand All @@ -15,8 +17,8 @@ import com.justai.jaicf.reactions.jaicp.JaicpCompatibleAsyncReactions
* @throws NoOperatorsOnlineException when no livechat operators are available
* @throws NoOperatorChannelConfiguredException when current channel has no livechat configured
* */
fun JaicpCompatibleAsyncReactions.switchToOperator(message: String) =
switchToOperator(LiveChatSwitchReply(firstMessage = message))
fun JaicpCompatibleAsyncReactions.switchToLiveChat(message: String) =
switchToLiveChat(LiveChatSwitchReply(firstMessage = message))


/**
Expand All @@ -29,7 +31,42 @@ fun JaicpCompatibleAsyncReactions.switchToOperator(message: String) =
* @throws NoOperatorsOnlineException when no livechat operators are available
* @throws NoOperatorChannelConfiguredException when current channel has no livechat configured
* */
fun JaicpCompatibleAsyncReactions.switchToOperator(reply: LiveChatSwitchReply) =
LiveChatInitRequest.create(loggingContext, reply)?.let {
ChatAdapterConnector.getIfExists()?.initLiveChat(it)
}
fun JaicpCompatibleAsyncReactions.switchToLiveChat(reply: LiveChatSwitchReply): SwitchReaction? {
val switchRequest = LiveChatInitRequest.create(loggingContext, reply) ?: return null
val connector = ChatAdapterConnector.getIfExists() ?: return null
connector.initLiveChat(switchRequest)
return SwitchReaction.fromReply(switchRequest.switchData, loggingContext.botContext.dialogContext.currentState)
.also { loggingContext.reactions.add(it) }
}

data class SwitchReaction(
val firstMessage: String? = null,
val closeChatPhrases: List<String> = emptyList(),
val appendCloseChatButton: Boolean = false,
val ignoreOffline: Boolean = false,
val oneTimeMessage: Boolean = false,
val destination: String? = null,
val lastMessage: String? = null,
val attributes: JsonObject? = null,
val hiddenAttributes: JsonObject? = null,
val sendMessagesToOperator: Boolean = false,
val sendMessageHistoryAmount: Int? = null,
override val fromState: String
) : Reaction(fromState) {
companion object {
fun fromReply(switchReply: LiveChatSwitchReply, state: String) = SwitchReaction(
firstMessage = switchReply.firstMessage,
closeChatPhrases = switchReply.closeChatPhrases,
appendCloseChatButton = switchReply.appendCloseChatButton,
ignoreOffline = switchReply.ignoreOffline,
oneTimeMessage = switchReply.oneTimeMessage,
destination = switchReply.destination,
lastMessage = switchReply.lastMessage,
attributes = switchReply.attributes,
hiddenAttributes = switchReply.hiddenAttributes,
sendMessageHistoryAmount = switchReply.sendMessageHistoryAmount,
sendMessagesToOperator = switchReply.sendMessagesToOperator,
fromState = state
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.justai.jaicf.channel.jaicp.JaicpTestChannel
import com.justai.jaicf.channel.jaicp.ScenarioFactory.echoWithAction
import com.justai.jaicf.channel.jaicp.channels.ChatWidgetChannel
import com.justai.jaicf.channel.jaicp.dto.LiveChatSwitchReply
import com.justai.jaicf.channel.jaicp.reactions.switchToOperator
import com.justai.jaicf.channel.jaicp.reactions.switchToLiveChat
import com.justai.jaicf.reactions.jaicp.jaicpAsync
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
Expand All @@ -18,7 +18,7 @@ internal class JaicpLiveChatsTest : JaicpBaseTest() {
fun `001 livechat should switch to operator`() {
val message = "My First Message"
val scenario = echoWithAction {
reactions.jaicpAsync?.switchToOperator(message)
reactions.jaicpAsync?.switchToLiveChat(message)
}
JaicpTestChannel(scenario, ChatWidgetChannel).process(request)

Expand All @@ -36,7 +36,7 @@ internal class JaicpLiveChatsTest : JaicpBaseTest() {
oneTimeMessage = false
)
val scenario = echoWithAction {
reactions.jaicpAsync?.switchToOperator(expected)
reactions.jaicpAsync?.switchToLiveChat(expected)
}
JaicpTestChannel(scenario, ChatWidgetChannel).process(request)

Expand Down
Loading

0 comments on commit 82f60f3

Please sign in to comment.