♻️ Export bot-engine code into its own package
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { ChoiceInputBlock, Variable } from '@typebot.io/schemas'
|
||||
import { executeCondition } from '../../logic/condition/executeCondition'
|
||||
|
||||
export const filterChoiceItems =
|
||||
(variables: Variable[]) =>
|
||||
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||
const filteredItems = block.items.filter((item) => {
|
||||
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
|
||||
return executeCondition(variables)(item.displayCondition.condition)
|
||||
|
||||
return true
|
||||
})
|
||||
return {
|
||||
...block,
|
||||
items: filteredItems,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
SessionState,
|
||||
VariableWithValue,
|
||||
ChoiceInputBlock,
|
||||
ItemType,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { filterChoiceItems } from './filterChoiceItems'
|
||||
import { deepParseVariables } from '../../../variables/deepParseVariables'
|
||||
import { transformStringVariablesToList } from '../../../variables/transformVariablesToList'
|
||||
import { updateVariablesInSession } from '../../../variables/updateVariablesInSession'
|
||||
|
||||
export const injectVariableValuesInButtonsInputBlock =
|
||||
(state: SessionState) =>
|
||||
(block: ChoiceInputBlock): ChoiceInputBlock => {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
if (block.options.dynamicVariableId) {
|
||||
const variable = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined
|
||||
if (!variable) return block
|
||||
const value = getVariableValue(state)(variable)
|
||||
return {
|
||||
...block,
|
||||
items: value.filter(isDefined).map((item, idx) => ({
|
||||
id: idx.toString(),
|
||||
type: ItemType.BUTTON,
|
||||
blockId: block.id,
|
||||
content: item,
|
||||
})),
|
||||
}
|
||||
}
|
||||
return deepParseVariables(variables)(filterChoiceItems(variables)(block))
|
||||
}
|
||||
|
||||
const getVariableValue =
|
||||
(state: SessionState) =>
|
||||
(variable: VariableWithValue): (string | null)[] => {
|
||||
if (!Array.isArray(variable.value)) {
|
||||
const { variables } = state.typebotsQueue[0].typebot
|
||||
const [transformedVariable] = transformStringVariablesToList(variables)([
|
||||
variable.id,
|
||||
])
|
||||
updateVariablesInSession(state)([transformedVariable])
|
||||
return transformedVariable.value as string[]
|
||||
}
|
||||
return variable.value
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ChoiceInputBlock, SessionState } from '@typebot.io/schemas'
|
||||
import { injectVariableValuesInButtonsInputBlock } from './injectVariableValuesInButtonsInputBlock'
|
||||
import { ParsedReply } from '../../../types'
|
||||
|
||||
export const parseButtonsReply =
|
||||
(state: SessionState) =>
|
||||
(inputValue: string, block: ChoiceInputBlock): ParsedReply => {
|
||||
const displayedItems =
|
||||
injectVariableValuesInButtonsInputBlock(state)(block).items
|
||||
if (block.options.isMultipleChoice) {
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.content?.length ?? 0) - (a.content?.length ?? 0)
|
||||
)
|
||||
const matchedItemsByContent = longestItemsFirst.reduce<{
|
||||
strippedInput: string
|
||||
matchedItemIds: string[]
|
||||
}>(
|
||||
(acc, item) => {
|
||||
if (
|
||||
item.content &&
|
||||
acc.strippedInput.toLowerCase().includes(item.content.toLowerCase())
|
||||
)
|
||||
return {
|
||||
strippedInput: acc.strippedInput.replace(item.content ?? '', ''),
|
||||
matchedItemIds: [...acc.matchedItemIds, item.id],
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{
|
||||
strippedInput: inputValue.trim(),
|
||||
matchedItemIds: [],
|
||||
}
|
||||
)
|
||||
const remainingItems = displayedItems.filter(
|
||||
(item) => !matchedItemsByContent.matchedItemIds.includes(item.id)
|
||||
)
|
||||
const matchedItemsByIndex = remainingItems.reduce<{
|
||||
strippedInput: string
|
||||
matchedItemIds: string[]
|
||||
}>(
|
||||
(acc, item, idx) => {
|
||||
if (acc.strippedInput.includes(`${idx + 1}`))
|
||||
return {
|
||||
strippedInput: acc.strippedInput.replace(`${idx + 1}`, ''),
|
||||
matchedItemIds: [...acc.matchedItemIds, item.id],
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{
|
||||
strippedInput: matchedItemsByContent.strippedInput,
|
||||
matchedItemIds: [],
|
||||
}
|
||||
)
|
||||
const matchedItems = displayedItems.filter((item) =>
|
||||
[
|
||||
...matchedItemsByContent.matchedItemIds,
|
||||
...matchedItemsByIndex.matchedItemIds,
|
||||
].includes(item.id)
|
||||
)
|
||||
if (matchedItems.length === 0) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItems.map((item) => item.content).join(', '),
|
||||
}
|
||||
}
|
||||
if (state.whatsApp) {
|
||||
const matchedItem = displayedItems.find((item) => item.id === inputValue)
|
||||
if (!matchedItem) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItem.content ?? '',
|
||||
}
|
||||
}
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.content?.length ?? 0) - (a.content?.length ?? 0)
|
||||
)
|
||||
const matchedItem = longestItemsFirst.find(
|
||||
(item) =>
|
||||
item.content &&
|
||||
inputValue.toLowerCase().trim() === item.content.toLowerCase().trim()
|
||||
)
|
||||
if (!matchedItem) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItem.content ?? '',
|
||||
}
|
||||
}
|
||||
48
packages/bot-engine/blocks/inputs/date/parseDateInput.ts
Normal file
48
packages/bot-engine/blocks/inputs/date/parseDateInput.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { getPrefilledInputValue } from '../../../getPrefilledValue'
|
||||
import {
|
||||
DateInputBlock,
|
||||
DateInputOptions,
|
||||
SessionState,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { deepParseVariables } from '../../../variables/deepParseVariables'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
|
||||
export const parseDateInput =
|
||||
(state: SessionState) => (block: DateInputBlock) => {
|
||||
return {
|
||||
...block,
|
||||
options: {
|
||||
...deepParseVariables(state.typebotsQueue[0].typebot.variables)(
|
||||
block.options
|
||||
),
|
||||
min: parseDateLimit(
|
||||
block.options.min,
|
||||
block.options.hasTime,
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
),
|
||||
max: parseDateLimit(
|
||||
block.options.max,
|
||||
block.options.hasTime,
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
),
|
||||
},
|
||||
prefilledValue: getPrefilledInputValue(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block),
|
||||
}
|
||||
}
|
||||
|
||||
const parseDateLimit = (
|
||||
limit: DateInputOptions['min'] | DateInputOptions['max'],
|
||||
hasTime: DateInputOptions['hasTime'],
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!limit) return
|
||||
const parsedLimit = parseVariables(variables)(limit)
|
||||
const dateIsoNoSecondsRegex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d/
|
||||
const matchDateTime = parsedLimit.match(dateIsoNoSecondsRegex)
|
||||
if (matchDateTime)
|
||||
return hasTime ? matchDateTime[0] : matchDateTime[0].slice(0, 10)
|
||||
return parsedLimit
|
||||
}
|
||||
51
packages/bot-engine/blocks/inputs/date/parseDateReply.ts
Normal file
51
packages/bot-engine/blocks/inputs/date/parseDateReply.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ParsedReply } from '../../../types'
|
||||
import { DateInputBlock } from '@typebot.io/schemas'
|
||||
import { parse as chronoParse } from 'chrono-node'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export const parseDateReply = (
|
||||
reply: string,
|
||||
block: DateInputBlock
|
||||
): ParsedReply => {
|
||||
const parsedDate = chronoParse(reply)
|
||||
if (parsedDate.length === 0) return { status: 'fail' }
|
||||
const formatString =
|
||||
block.options.format ??
|
||||
(block.options.hasTime ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy')
|
||||
|
||||
const detectedStartDate = parseDateWithNeutralTimezone(
|
||||
parsedDate[0].start.date()
|
||||
)
|
||||
const startDate = format(detectedStartDate, formatString)
|
||||
|
||||
const detectedEndDate = parsedDate[0].end?.date()
|
||||
? parseDateWithNeutralTimezone(parsedDate[0].end?.date())
|
||||
: undefined
|
||||
const endDate = detectedEndDate
|
||||
? format(detectedEndDate, formatString)
|
||||
: undefined
|
||||
|
||||
if (block.options.isRange && !endDate) return { status: 'fail' }
|
||||
|
||||
if (
|
||||
block.options.max &&
|
||||
(detectedStartDate > new Date(block.options.max) ||
|
||||
(detectedEndDate && detectedEndDate > new Date(block.options.max)))
|
||||
)
|
||||
return { status: 'fail' }
|
||||
|
||||
if (
|
||||
block.options.min &&
|
||||
(detectedStartDate < new Date(block.options.min) ||
|
||||
(detectedEndDate && detectedEndDate < new Date(block.options.min)))
|
||||
)
|
||||
return { status: 'fail' }
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
reply: block.options.isRange ? `${startDate} to ${endDate}` : startDate,
|
||||
}
|
||||
}
|
||||
|
||||
const parseDateWithNeutralTimezone = (date: Date) =>
|
||||
new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000)
|
||||
4
packages/bot-engine/blocks/inputs/email/validateEmail.ts
Normal file
4
packages/bot-engine/blocks/inputs/email/validateEmail.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
const emailRegex =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
export const validateEmail = (email: string) => emailRegex.test(email)
|
||||
@@ -0,0 +1 @@
|
||||
export const validateNumber = (inputValue: string) => !isNaN(Number(inputValue))
|
||||
@@ -0,0 +1,124 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import {
|
||||
PaymentInputOptions,
|
||||
PaymentInputRuntimeOptions,
|
||||
SessionState,
|
||||
StripeCredentials,
|
||||
} from '@typebot.io/schemas'
|
||||
import Stripe from 'stripe'
|
||||
import { decrypt } from '@typebot.io/lib/api/encryption'
|
||||
import { parseVariables } from '../../../variables/parseVariables'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
|
||||
export const computePaymentInputRuntimeOptions =
|
||||
(state: SessionState) => (options: PaymentInputOptions) =>
|
||||
createStripePaymentIntent(state)(options)
|
||||
|
||||
const createStripePaymentIntent =
|
||||
(state: SessionState) =>
|
||||
async (options: PaymentInputOptions): Promise<PaymentInputRuntimeOptions> => {
|
||||
const {
|
||||
resultId,
|
||||
typebot: { variables },
|
||||
} = state.typebotsQueue[0]
|
||||
const isPreview = !resultId
|
||||
if (!options.credentialsId)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Missing credentialsId',
|
||||
})
|
||||
const stripeKeys = await getStripeInfo(options.credentialsId)
|
||||
if (!stripeKeys)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Credentials not found',
|
||||
})
|
||||
const stripe = new Stripe(
|
||||
isPreview && stripeKeys?.test?.secretKey
|
||||
? stripeKeys.test.secretKey
|
||||
: stripeKeys.live.secretKey,
|
||||
{ apiVersion: '2022-11-15' }
|
||||
)
|
||||
const amount = Math.round(
|
||||
Number(parseVariables(variables)(options.amount)) *
|
||||
(isZeroDecimalCurrency(options.currency) ? 1 : 100)
|
||||
)
|
||||
if (isNaN(amount))
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message:
|
||||
'Could not parse amount, make sure your block is configured correctly',
|
||||
})
|
||||
// Create a PaymentIntent with the order amount and currency
|
||||
const receiptEmail = parseVariables(variables)(
|
||||
options.additionalInformation?.email
|
||||
)
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount,
|
||||
currency: options.currency,
|
||||
receipt_email: receiptEmail === '' ? undefined : receiptEmail,
|
||||
description: options.additionalInformation?.description,
|
||||
automatic_payment_methods: {
|
||||
enabled: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!paymentIntent.client_secret)
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Could not create payment intent',
|
||||
})
|
||||
|
||||
const priceFormatter = new Intl.NumberFormat(
|
||||
options.currency === 'EUR' ? 'fr-FR' : undefined,
|
||||
{
|
||||
style: 'currency',
|
||||
currency: options.currency,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
paymentIntentSecret: paymentIntent.client_secret,
|
||||
publicKey:
|
||||
isPreview && stripeKeys.test?.publicKey
|
||||
? stripeKeys.test.publicKey
|
||||
: stripeKeys.live.publicKey,
|
||||
amountLabel: priceFormatter.format(
|
||||
amount / (isZeroDecimalCurrency(options.currency) ? 1 : 100)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const getStripeInfo = async (
|
||||
credentialsId: string
|
||||
): Promise<StripeCredentials['data'] | undefined> => {
|
||||
const credentials = await prisma.credentials.findUnique({
|
||||
where: { id: credentialsId },
|
||||
})
|
||||
if (!credentials) return
|
||||
return (await decrypt(
|
||||
credentials.data,
|
||||
credentials.iv
|
||||
)) as StripeCredentials['data']
|
||||
}
|
||||
|
||||
// https://stripe.com/docs/currencies#zero-decimal
|
||||
const isZeroDecimalCurrency = (currency: string) =>
|
||||
[
|
||||
'BIF',
|
||||
'CLP',
|
||||
'DJF',
|
||||
'GNF',
|
||||
'JPY',
|
||||
'KMF',
|
||||
'KRW',
|
||||
'MGA',
|
||||
'PYG',
|
||||
'RWF',
|
||||
'UGX',
|
||||
'VND',
|
||||
'VUV',
|
||||
'XAF',
|
||||
'XOF',
|
||||
'XPF',
|
||||
].includes(currency)
|
||||
16
packages/bot-engine/blocks/inputs/phone/formatPhoneNumber.ts
Normal file
16
packages/bot-engine/blocks/inputs/phone/formatPhoneNumber.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
CountryCode,
|
||||
findPhoneNumbersInText,
|
||||
isSupportedCountry,
|
||||
} from 'libphonenumber-js'
|
||||
|
||||
export const formatPhoneNumber = (
|
||||
phoneNumber: string,
|
||||
defaultCountryCode?: string
|
||||
) =>
|
||||
findPhoneNumbersInText(
|
||||
phoneNumber,
|
||||
defaultCountryCode && isSupportedCountry(defaultCountryCode)
|
||||
? (defaultCountryCode as CountryCode)
|
||||
: undefined
|
||||
).at(0)?.number.number
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PictureChoiceBlock, Variable } from '@typebot.io/schemas'
|
||||
import { executeCondition } from '../../logic/condition/executeCondition'
|
||||
|
||||
export const filterPictureChoiceItems =
|
||||
(variables: Variable[]) =>
|
||||
(block: PictureChoiceBlock): PictureChoiceBlock => {
|
||||
const filteredItems = block.items.filter((item) => {
|
||||
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
|
||||
return executeCondition(variables)(item.displayCondition.condition)
|
||||
|
||||
return true
|
||||
})
|
||||
return {
|
||||
...block,
|
||||
items: filteredItems,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
VariableWithValue,
|
||||
ItemType,
|
||||
PictureChoiceBlock,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { filterPictureChoiceItems } from './filterPictureChoiceItems'
|
||||
import { deepParseVariables } from '../../../variables/deepParseVariables'
|
||||
|
||||
export const injectVariableValuesInPictureChoiceBlock =
|
||||
(variables: Variable[]) =>
|
||||
(block: PictureChoiceBlock): PictureChoiceBlock => {
|
||||
if (
|
||||
block.options.dynamicItems?.isEnabled &&
|
||||
block.options.dynamicItems.pictureSrcsVariableId
|
||||
) {
|
||||
const pictureSrcsVariable = variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicItems?.pictureSrcsVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined
|
||||
if (!pictureSrcsVariable || typeof pictureSrcsVariable.value === 'string')
|
||||
return block
|
||||
const titlesVariable = block.options.dynamicItems.titlesVariableId
|
||||
? (variables.find(
|
||||
(variable) =>
|
||||
variable.id === block.options.dynamicItems?.titlesVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined)
|
||||
: undefined
|
||||
const descriptionsVariable = block.options.dynamicItems
|
||||
.descriptionsVariableId
|
||||
? (variables.find(
|
||||
(variable) =>
|
||||
variable.id ===
|
||||
block.options.dynamicItems?.descriptionsVariableId &&
|
||||
isDefined(variable.value)
|
||||
) as VariableWithValue | undefined)
|
||||
: undefined
|
||||
return {
|
||||
...block,
|
||||
items: pictureSrcsVariable.value
|
||||
.filter(isDefined)
|
||||
.map((pictureSrc, idx) => ({
|
||||
id: idx.toString(),
|
||||
type: ItemType.PICTURE_CHOICE,
|
||||
blockId: block.id,
|
||||
pictureSrc,
|
||||
title: titlesVariable?.value?.[idx] ?? '',
|
||||
description: descriptionsVariable?.value?.[idx] ?? '',
|
||||
})),
|
||||
}
|
||||
}
|
||||
return deepParseVariables(variables)(
|
||||
filterPictureChoiceItems(variables)(block)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { PictureChoiceBlock, SessionState } from '@typebot.io/schemas'
|
||||
import { ParsedReply } from '../../../types'
|
||||
import { injectVariableValuesInPictureChoiceBlock } from './injectVariableValuesInPictureChoiceBlock'
|
||||
|
||||
export const parsePictureChoicesReply =
|
||||
(state: SessionState) =>
|
||||
(inputValue: string, block: PictureChoiceBlock): ParsedReply => {
|
||||
const displayedItems = injectVariableValuesInPictureChoiceBlock(
|
||||
state.typebotsQueue[0].typebot.variables
|
||||
)(block).items
|
||||
if (block.options.isMultipleChoice) {
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.title?.length ?? 0) - (a.title?.length ?? 0)
|
||||
)
|
||||
const matchedItemsByContent = longestItemsFirst.reduce<{
|
||||
strippedInput: string
|
||||
matchedItemIds: string[]
|
||||
}>(
|
||||
(acc, item) => {
|
||||
if (
|
||||
item.title &&
|
||||
acc.strippedInput.toLowerCase().includes(item.title.toLowerCase())
|
||||
)
|
||||
return {
|
||||
strippedInput: acc.strippedInput.replace(item.title ?? '', ''),
|
||||
matchedItemIds: [...acc.matchedItemIds, item.id],
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{
|
||||
strippedInput: inputValue.trim(),
|
||||
matchedItemIds: [],
|
||||
}
|
||||
)
|
||||
const remainingItems = displayedItems.filter(
|
||||
(item) => !matchedItemsByContent.matchedItemIds.includes(item.id)
|
||||
)
|
||||
const matchedItemsByIndex = remainingItems.reduce<{
|
||||
strippedInput: string
|
||||
matchedItemIds: string[]
|
||||
}>(
|
||||
(acc, item, idx) => {
|
||||
if (acc.strippedInput.includes(`${idx + 1}`))
|
||||
return {
|
||||
strippedInput: acc.strippedInput.replace(`${idx + 1}`, ''),
|
||||
matchedItemIds: [...acc.matchedItemIds, item.id],
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{
|
||||
strippedInput: matchedItemsByContent.strippedInput,
|
||||
matchedItemIds: [],
|
||||
}
|
||||
)
|
||||
|
||||
const matchedItems = displayedItems.filter((item) =>
|
||||
[
|
||||
...matchedItemsByContent.matchedItemIds,
|
||||
...matchedItemsByIndex.matchedItemIds,
|
||||
].includes(item.id)
|
||||
)
|
||||
|
||||
if (matchedItems.length === 0) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItems
|
||||
.map((item) => item.title ?? item.pictureSrc ?? '')
|
||||
.join(', '),
|
||||
}
|
||||
}
|
||||
if (state.whatsApp) {
|
||||
const matchedItem = displayedItems.find((item) => item.id === inputValue)
|
||||
if (!matchedItem) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItem.title ?? matchedItem.pictureSrc ?? '',
|
||||
}
|
||||
}
|
||||
const longestItemsFirst = [...displayedItems].sort(
|
||||
(a, b) => (b.title?.length ?? 0) - (a.title?.length ?? 0)
|
||||
)
|
||||
const matchedItem = longestItemsFirst.find(
|
||||
(item) =>
|
||||
item.title &&
|
||||
item.title
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.includes(inputValue.toLowerCase().trim())
|
||||
)
|
||||
if (!matchedItem) return { status: 'fail' }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: matchedItem.title ?? matchedItem.pictureSrc ?? '',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { RatingInputBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const validateRatingReply = (reply: string, block: RatingInputBlock) =>
|
||||
Number(reply) <= block.options.length
|
||||
4
packages/bot-engine/blocks/inputs/url/validateUrl.ts
Normal file
4
packages/bot-engine/blocks/inputs/url/validateUrl.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
const urlRegex =
|
||||
/^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$/
|
||||
|
||||
export const validateUrl = (url: string) => urlRegex.test(url)
|
||||
Reference in New Issue
Block a user