All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bot-connector-whatsapp.24.9.0.source-code.WhatsAppBuilder.kt Maven / Gradle / Ivy

There is a newer version: 24.9.4
Show newest version
/*
 * Copyright (C) 2017/2021 e-voyageurs technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ai.tock.bot.connector.whatsapp

import ai.tock.bot.connector.ConnectorMessage
import ai.tock.bot.connector.ConnectorType
import ai.tock.bot.connector.whatsapp.model.common.WhatsAppTextBody
import ai.tock.bot.connector.whatsapp.model.send.QuickReply
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotAction
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotActionButton
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotActionButtonReply
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotActionSection
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotAttachment
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotBody
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotImageMessage
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotInteractive
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotInteractiveMessage
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotInteractiveType
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotMessage
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotMessageInteractiveMessage
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotRecipientType
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotRow
import ai.tock.bot.connector.whatsapp.model.send.WhatsAppBotTextMessage
import ai.tock.bot.connector.whatsapp.model.webhook.WhatsAppComponent
import ai.tock.bot.connector.whatsapp.model.webhook.WhatsAppLanguage
import ai.tock.bot.connector.whatsapp.model.webhook.WhatsAppTemplate
import ai.tock.bot.definition.IntentAware
import ai.tock.bot.definition.Parameters
import ai.tock.bot.definition.StoryHandlerDefinition
import ai.tock.bot.definition.StoryStep
import ai.tock.bot.engine.BotBus
import ai.tock.bot.engine.Bus
import ai.tock.bot.engine.I18nTranslator
import ai.tock.bot.engine.action.SendChoice
import mu.KotlinLogging

private val logger = KotlinLogging.logger {}

internal const val WHATS_APP_CONNECTOR_TYPE_ID = "whatsapp"
private const val WHATS_APP_BUTTONS_TITLE_MAX_LENGTH = 20
private const val WHATS_APP_BUTTONS_ID_MAX_LENGTH = 256
private const val WHATS_APP_SECTION_TITLE_MAX_LENGTH = 24
private const val WHATS_APP_ROW_TITLE_MAX_LENGTH = 24
private const val WHATS_APP_ROW_ID_MAX_LENGTH = 200
private const val WHATS_APP_ROW_DESCRIPTION_MAX_LENGTH = 72
private const val WHATS_APP_MAX_ROWS = 10
private const val WHATS_APP_MAX_SECTIONS = 10

/**
 * The WhatsApp connector type.
 */
val whatsAppConnectorType = ConnectorType(WHATS_APP_CONNECTOR_TYPE_ID)

/**
 * Sends an WhatsApp message only if the [ConnectorType] of the current [BotBus] is [whatsAppConnectorType].
 */
fun > T.sendToWhatsApp(
    messageProvider: T.() -> WhatsAppBotMessage,
    delay: Long = defaultDelay(currentAnswerIndex)
): T {
    if (isCompatibleWith(whatsAppConnectorType)) {
        withMessage(messageProvider(this))
        send(delay)
    }
    return this
}

/**
 * Sends an WhatsApp message as last bot answer, only if the [ConnectorType] of the current [BotBus] is [whatsAppConnectorType].
 */
fun > T.endForWhatsApp(
    messageProvider: T.() -> WhatsAppBotMessage,
    delay: Long = defaultDelay(currentAnswerIndex)
): T {
    if (isCompatibleWith(whatsAppConnectorType)) {
        withMessage(messageProvider(this))
        end(delay)
    }
    return this
}

/**
 * Adds a WhatsApp [ConnectorMessage] if the current connector is WhatsApp.
 * You need to call [BotBus.send] or [BotBus.end] later to send this message.
 */
fun > T.withWhatsApp(messageProvider: () -> WhatsAppBotMessage): T {
    return withMessage(whatsAppConnectorType, messageProvider)
}

/**
 * Creates a [WhatsAppBotTextMessage].
 *
 * @param text the text sent
 * @param previewUrl is preview mode is used?
 */
fun BotBus.whatsAppText(
    text: CharSequence,
    previewUrl: Boolean = false
): WhatsAppBotTextMessage =
    WhatsAppBotTextMessage(
        text = WhatsAppTextBody(translate(text).toString()),
        recipientType = (connectorData.callback as? WhatsAppConnectorCallback)?.recipientType
            ?: WhatsAppBotRecipientType.individual,
        userId = userId.id,
        previewUrl = previewUrl
    )

/**
 * Creates a [WhatsAppBotImageMessage].
 */
fun BotBus.whatsAppImage(
    byteImages: ByteArray,
    contentType: String = "image/png",
    caption: CharSequence? = null
): WhatsAppBotImageMessage =
    WhatsAppBotImageMessage(
        image = WhatsAppBotAttachment(
            byteImages,
            contentType,
            caption?.let { translate(it).toString() }
        ),
        recipientType = (connectorData.callback as? WhatsAppConnectorCallback)?.recipientType
            ?: WhatsAppBotRecipientType.individual,
        userId = userId.id
    )

/**
 * Creates a [WhatsAppBotInteractiveMessage]
 */
fun BotBus.whatsAppInteractiveMessage(
    nameSpace: String,
    templateName: String,
    components: List? = emptyList()
): WhatsAppBotInteractiveMessage =
    WhatsAppBotInteractiveMessage(
        template = buildTemplate(nameSpace, templateName, components),
        recipientType = (connectorData.callback as? WhatsAppConnectorCallback)?.recipientType
            ?: WhatsAppBotRecipientType.individual,
        userId = userId.id
    )

fun I18nTranslator.replyButtonMessage(
    text: CharSequence,
    vararg replies: QuickReply
) : WhatsAppBotMessageInteractiveMessage = replyButtonMessage(text, replies.toList())

fun I18nTranslator.replyButtonMessage(
    text: CharSequence,
    replies: List
) : WhatsAppBotMessageInteractiveMessage = WhatsAppBotMessageInteractiveMessage(
    recipientType = WhatsAppBotRecipientType.individual,
    interactive = WhatsAppBotInteractive(
        type = WhatsAppBotInteractiveType.button,
        body = WhatsAppBotBody(translate(text).toString()),
        action = WhatsAppBotAction(
            buttons = replies.map {
                WhatsAppBotActionButton(
                    reply = WhatsAppBotActionButtonReply(
                        id = it.payload.checkLength(WHATS_APP_BUTTONS_ID_MAX_LENGTH),
                        title = translate(it.title).toString().checkLength(WHATS_APP_BUTTONS_TITLE_MAX_LENGTH),
                    )
                )
            }
        )
    )
)

fun I18nTranslator.completeListMessage(
    text: CharSequence,
    button: CharSequence,
    vararg sections: WhatsAppBotActionSection
) : WhatsAppBotMessageInteractiveMessage = completeListMessage(text, button, sections.toList())

fun I18nTranslator.completeListMessage(
    text: CharSequence,
    button: CharSequence,
    sections: List,
) : WhatsAppBotMessageInteractiveMessage = WhatsAppBotMessageInteractiveMessage(
    recipientType = WhatsAppBotRecipientType.individual,
    interactive = WhatsAppBotInteractive(
        type = WhatsAppBotInteractiveType.list,
        body = WhatsAppBotBody(translate(text).toString()),
        action = WhatsAppBotAction(
            button = translate(button).toString().checkLength(WHATS_APP_BUTTONS_TITLE_MAX_LENGTH),
            sections = sections.map {
                WhatsAppBotActionSection(
                    title = translate(it.title).toString().checkLength(WHATS_APP_SECTION_TITLE_MAX_LENGTH),
                    rows = it.rows?.map { row ->
                        WhatsAppBotRow(
                            id = row.id.checkLength(WHATS_APP_ROW_ID_MAX_LENGTH),
                            title = translate(row.title).toString().checkLength(WHATS_APP_ROW_TITLE_MAX_LENGTH),
                            description = translate(row.description).toString().checkLength(WHATS_APP_ROW_DESCRIPTION_MAX_LENGTH)
                        )
                    }
                )
            }
        )
    )
).also {
    if ((it.interactive.action?.sections?.flatMap { s -> s.rows ?: listOf() }?.count() ?: 0) > WHATS_APP_MAX_ROWS) {
        error("a list message is limited to $WHATS_APP_MAX_ROWS rows across all sections.")
    }
    if ((it.interactive.action?.sections?.count() ?: 0) > WHATS_APP_MAX_SECTIONS) {
        error("sections count in list message should not exceed $WHATS_APP_MAX_SECTIONS.")
    }
    if ((it.interactive.action?.button?.count() ?: 0) > WHATS_APP_BUTTONS_TITLE_MAX_LENGTH) {
        error("button text ${it.interactive.action?.button} should not exceed $WHATS_APP_BUTTONS_TITLE_MAX_LENGTH chars.")
    }
}

fun I18nTranslator.listMessage(
    text: CharSequence,
    button: CharSequence,
    vararg replies: QuickReply
) : WhatsAppBotMessageInteractiveMessage =
        listMessage(text, button, replies.toList())

fun I18nTranslator.listMessage(
    text: CharSequence,
    button: CharSequence,
    replies: List
) : WhatsAppBotMessageInteractiveMessage =
    completeListMessage(
        text, button, WhatsAppBotActionSection(rows = replies.map {
            WhatsAppBotRow(
                id = it.payload,
                title = it.title,
                description = it.subTitle
            )
        })
    )

fun I18nTranslator.nlpQuickReply(
    title: CharSequence,
    subTitle: CharSequence? = null,
    textToSend: CharSequence = title,
) : QuickReply = QuickReply(
        translate(title).toString(),
        SendChoice.encodeNlpChoiceId(translate(textToSend).toString()),
        translate(subTitle).toString(),
)

fun > T.quickReply(
    title: CharSequence,
    subTitle: CharSequence? = null,
    targetIntent: IntentAware,
    parameters: Parameters
): QuickReply =
    quickReply(title, subTitle, targetIntent, stepName, parameters.toMap())

fun > T.quickReply(
    title: CharSequence,
    subTitle: CharSequence? = null,
    targetIntent: IntentAware,
    step: StoryStep? = null,
    vararg parameters: Pair
) : QuickReply = quickReply(title, subTitle, targetIntent.wrappedIntent(), step?.name, parameters.toMap())

fun > T.quickReply(
    title: CharSequence,
    subTitle: CharSequence? = null,
    targetIntent: IntentAware,
    step: String? = null,
    parameters: Map = mapOf()
) : QuickReply =
    quickReply(title, subTitle, targetIntent, step, parameters) { intent, s, params ->
        SendChoice.encodeChoiceId(intent, s, params, null, null, sourceAppId = null)
    }

private fun I18nTranslator.quickReply(
    title: CharSequence,
    subTitle: CharSequence? = null,
    targetIntent: IntentAware,
    step: String? = null,
    parameters: Map,
    payloadEncoder: (IntentAware, String?, Map) -> String
) : QuickReply = QuickReply(
        translate(title).toString(),
        payloadEncoder.invoke(targetIntent, step, parameters),
        translate(subTitle).toString()
)

private fun buildTemplate(
    nameSpace: String,
    templateName: String,
    components: List?
): WhatsAppTemplate {
    return WhatsAppTemplate(
        namespace = nameSpace,
        name = templateName,
        language = WhatsAppLanguage(
            code = "fr",
            policy = "deterministic"
        ),
        components = components
    )
}

private fun String.checkLength(maxLength: Int) : String {
    if (maxLength > 0 && this.length > maxLength) {
        error("text $this should not exceed $maxLength chars.")
    }
    return this
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy