2
0
Files
bot/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts
2023-09-20 15:42:34 +02:00

193 lines
4.8 KiB
TypeScript

import { SessionState } from '@typebot.io/schemas'
import {
WhatsAppCredentials,
WhatsAppIncomingMessage,
} from '@typebot.io/schemas/features/whatsapp'
import { env } from '@typebot.io/env'
import { sendChatReplyToWhatsApp } from './sendChatReplyToWhatsApp'
import { startWhatsAppSession } from './startWhatsAppSession'
import { downloadMedia } from './downloadMedia'
import { getSession } from '../queries/getSession'
import { continueBotFlow } from '../continueBotFlow'
import { decrypt } from '@typebot.io/lib/api'
import { saveStateToDatabase } from '../saveStateToDatabase'
import prisma from '@typebot.io/lib/prisma'
export const resumeWhatsAppFlow = async ({
receivedMessage,
sessionId,
workspaceId,
phoneNumberId,
contact,
}: {
receivedMessage: WhatsAppIncomingMessage
sessionId: string
phoneNumberId: string
workspaceId?: string
contact: NonNullable<SessionState['whatsApp']>['contact']
}) => {
const messageSendDate = new Date(Number(receivedMessage.timestamp) * 1000)
const messageSentBefore3MinutesAgo =
messageSendDate.getTime() < Date.now() - 180000
if (messageSentBefore3MinutesAgo) {
console.log('Message is too old', messageSendDate.getTime())
return {
message: 'Message received',
}
}
const session = await getSession(sessionId)
const initialCredentials = session
? await getCredentials(phoneNumberId)(session.state)
: undefined
const { typebot, resultId } = session?.state.typebotsQueue[0] ?? {}
const messageContent = await getIncomingMessageContent({
message: receivedMessage,
systemUserToken: initialCredentials?.systemUserAccessToken,
downloadPath:
typebot && resultId
? `typebots/${typebot.id}/results/${resultId}`
: undefined,
})
const isPreview = workspaceId === undefined
const sessionState =
isPreview && session?.state
? ({
...session?.state,
whatsApp: {
contact,
},
} satisfies SessionState)
: session?.state
const resumeResponse = sessionState
? await continueBotFlow(sessionState)(messageContent)
: workspaceId
? await startWhatsAppSession({
message: receivedMessage,
sessionId,
workspaceId,
phoneNumberId,
contact,
})
: undefined
if (!resumeResponse) {
console.error('Could not find or create session', sessionId)
return {
message: 'Message received',
}
}
const credentials =
initialCredentials ??
(await getCredentials(phoneNumberId)(resumeResponse.newSessionState))
if (!credentials) {
console.error('Could not find credentials')
return {
message: 'Message received',
}
}
const { input, logs, newSessionState, messages, clientSideActions } =
resumeResponse
await sendChatReplyToWhatsApp({
to: receivedMessage.from,
messages,
input,
typingEmulation: newSessionState.typingEmulation,
clientSideActions,
credentials,
})
await saveStateToDatabase({
isFirstSave: !session,
clientSideActions: [],
input,
logs,
session: {
id: sessionId,
state: {
...newSessionState,
currentBlock: !input ? undefined : newSessionState.currentBlock,
},
},
})
return {
message: 'Message received',
}
}
const getIncomingMessageContent = async ({
message,
systemUserToken,
downloadPath,
}: {
message: WhatsAppIncomingMessage
systemUserToken: string | undefined
downloadPath?: string
}): Promise<string | undefined> => {
switch (message.type) {
case 'text':
return message.text.body
case 'button':
return message.button.text
case 'interactive': {
return message.interactive.button_reply.id
}
case 'document':
case 'audio':
return
case 'video':
case 'image':
if (!systemUserToken || !downloadPath) return ''
return downloadMedia({
mediaId: 'video' in message ? message.video.id : message.image.id,
systemUserToken,
downloadPath,
})
}
}
const getCredentials =
(phoneNumberId: string) =>
async (
state: SessionState
): Promise<WhatsAppCredentials['data'] | undefined> => {
const isPreview = !state.typebotsQueue[0].resultId
if (isPreview) {
if (!env.META_SYSTEM_USER_TOKEN) return
return {
systemUserAccessToken: env.META_SYSTEM_USER_TOKEN,
phoneNumberId,
}
}
if (!state.whatsApp) return
const credentials = await prisma.credentials.findUnique({
where: {
id: state.whatsApp.credentialsId,
},
select: {
data: true,
iv: true,
},
})
if (!credentials) return
const data = (await decrypt(
credentials.data,
credentials.iv
)) as WhatsAppCredentials['data']
return {
systemUserAccessToken: data.systemUserAccessToken,
phoneNumberId,
}
}