import prisma from '@typebot.io/lib/prisma' import { ChatReply, ComparisonOperators, LogicalOperator, PublicTypebot, SessionState, Settings, Typebot, } from '@typebot.io/schemas' import { WhatsAppCredentials, WhatsAppIncomingMessage, } from '@typebot.io/schemas/features/whatsapp' import { isNotDefined } from '@typebot.io/lib/utils' import { decrypt } from '@typebot.io/lib/api/encryption' import { startSession } from '../startSession' type Props = { message: WhatsAppIncomingMessage sessionId: string workspaceId?: string phoneNumberId: string contact: NonNullable['contact'] } export const startWhatsAppSession = async ({ message, workspaceId, phoneNumberId, contact, }: Props): Promise< | (ChatReply & { newSessionState: SessionState }) | undefined > => { const publicTypebotsWithWhatsAppEnabled = (await prisma.publicTypebot.findMany({ where: { typebot: { workspaceId, whatsAppPhoneNumberId: phoneNumberId }, }, select: { settings: true, typebot: { select: { publicId: true, }, }, }, })) as (Pick & { typebot: Pick })[] const botsWithWhatsAppEnabled = publicTypebotsWithWhatsAppEnabled.filter( (publicTypebot) => publicTypebot.typebot.publicId && publicTypebot.settings.whatsApp?.credentialsId ) const publicTypebot = botsWithWhatsAppEnabled.find( (publicTypebot) => publicTypebot.settings.whatsApp?.startCondition && messageMatchStartCondition( getIncomingMessageText(message), publicTypebot.settings.whatsApp?.startCondition ) ) ?? botsWithWhatsAppEnabled[0] if (isNotDefined(publicTypebot)) return const encryptedCredentials = await prisma.credentials.findUnique({ where: { id: publicTypebot.settings.whatsApp?.credentialsId, }, }) if (!encryptedCredentials) return const credentials = (await decrypt( encryptedCredentials?.data, encryptedCredentials?.iv )) as WhatsAppCredentials['data'] if (credentials.phoneNumberId !== phoneNumberId) return const session = await startSession({ startParams: { typebot: publicTypebot.typebot.publicId as string, }, userId: undefined, }) return { ...session, newSessionState: { ...session.newSessionState, whatsApp: { contact, credentialsId: publicTypebot?.settings.whatsApp ?.credentialsId as string, }, }, } } export const messageMatchStartCondition = ( message: string, startCondition: NonNullable['startCondition'] ) => { if (!startCondition) return true return startCondition.logicalOperator === LogicalOperator.AND ? startCondition.comparisons.every((comparison) => matchComparison( message, comparison.comparisonOperator, comparison.value ) ) : startCondition.comparisons.some((comparison) => matchComparison( message, comparison.comparisonOperator, comparison.value ) ) } const matchComparison = ( inputValue: string, comparisonOperator?: ComparisonOperators, value?: string ): boolean | undefined => { if (!comparisonOperator) return false switch (comparisonOperator) { case ComparisonOperators.CONTAINS: { if (!value) return false return inputValue .trim() .toLowerCase() .includes(value.trim().toLowerCase()) } case ComparisonOperators.EQUAL: { return inputValue === value } case ComparisonOperators.NOT_EQUAL: { return inputValue !== value } case ComparisonOperators.GREATER: { if (!value) return false return parseFloat(inputValue) > parseFloat(value) } case ComparisonOperators.LESS: { if (!value) return false return parseFloat(inputValue) < parseFloat(value) } case ComparisonOperators.IS_SET: { return inputValue.length > 0 } case ComparisonOperators.IS_EMPTY: { return inputValue.length === 0 } case ComparisonOperators.STARTS_WITH: { if (!value) return false return inputValue.toLowerCase().startsWith(value.toLowerCase()) } case ComparisonOperators.ENDS_WITH: { if (!value) return false return inputValue.toLowerCase().endsWith(value.toLowerCase()) } case ComparisonOperators.NOT_CONTAINS: { if (!value) return false return !inputValue .trim() .toLowerCase() .includes(value.trim().toLowerCase()) } } } const getIncomingMessageText = (message: WhatsAppIncomingMessage): string => { switch (message.type) { case 'text': return message.text.body case 'button': return message.button.text case 'interactive': { return message.interactive.button_reply.title } case 'video': case 'document': case 'audio': case 'image': { return '' } } }