🐛 (whatsapp) Fix WA preview not starting and accept audio and documents messages
This commit is contained in:
@ -10,15 +10,6 @@ import { OpenAI, ClientOptions } from 'openai'
|
|||||||
import { defaultOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
|
import { defaultOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
|
||||||
|
|
||||||
export const listModels = authenticatedProcedure
|
export const listModels = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/openai/models',
|
|
||||||
protect: true,
|
|
||||||
summary: 'List OpenAI models',
|
|
||||||
tags: ['OpenAI'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
credentialsId: z.string(),
|
credentialsId: z.string(),
|
||||||
@ -28,11 +19,6 @@ export const listModels = authenticatedProcedure
|
|||||||
type: z.enum(['gpt', 'tts']),
|
type: z.enum(['gpt', 'tts']),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
models: z.array(z.string()),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(
|
.query(
|
||||||
async ({
|
async ({
|
||||||
input: { credentialsId, workspaceId, baseUrl, apiVersion, type },
|
input: { credentialsId, workspaceId, baseUrl, apiVersion, type },
|
||||||
|
@ -8,31 +8,12 @@ import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integ
|
|||||||
import got from 'got'
|
import got from 'got'
|
||||||
|
|
||||||
export const listProjects = authenticatedProcedure
|
export const listProjects = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/zemantic-ai/projects',
|
|
||||||
protect: true,
|
|
||||||
summary: 'List Zemantic AI projects',
|
|
||||||
tags: ['ZemanticAi'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
credentialsId: z.string(),
|
credentialsId: z.string(),
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
projects: z.array(
|
|
||||||
z.object({
|
|
||||||
label: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
|
||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: { id: workspaceId },
|
where: { id: workspaceId },
|
||||||
|
@ -14,13 +14,6 @@ const inputShape = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const createCredentials = authenticatedProcedure
|
export const createCredentials = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/credentials',
|
|
||||||
protect: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
credentials: z.discriminatedUnion(
|
credentials: z.discriminatedUnion(
|
||||||
@ -31,11 +24,6 @@ export const createCredentials = authenticatedProcedure
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
credentialsId: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mutation(async ({ input: { credentials }, ctx: { user } }) => {
|
.mutation(async ({ input: { credentials }, ctx: { user } }) => {
|
||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
@ -11,11 +11,6 @@ export const deleteCredentials = authenticatedProcedure
|
|||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
credentialsId: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mutation(
|
.mutation(
|
||||||
async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
|
async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
|
||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
|
@ -12,11 +12,6 @@ export const listCredentials = authenticatedProcedure
|
|||||||
type: z.enum(enabledBlocks),
|
type: z.enum(enabledBlocks),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
credentials: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
|
||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { router } from '@/helpers/server/trpc'
|
|
||||||
import { createCredentials } from './createCredentials'
|
|
||||||
import { deleteCredentials } from './deleteCredentials'
|
|
||||||
import { listCredentials } from './listCredentials'
|
|
||||||
|
|
||||||
export const forgedCredentialsRouter = router({
|
|
||||||
createCredentials,
|
|
||||||
listCredentials,
|
|
||||||
deleteCredentials,
|
|
||||||
})
|
|
@ -15,13 +15,6 @@ export const fetchSelectItems = authenticatedProcedure
|
|||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
items: z.array(
|
|
||||||
z.string().or(z.object({ label: z.string(), value: z.string() }))
|
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(
|
.query(
|
||||||
async ({
|
async ({
|
||||||
input: { workspaceId, integrationId, fetcherId, options },
|
input: { workspaceId, integrationId, fetcherId, options },
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import { router } from '@/helpers/server/trpc'
|
import { router } from '@/helpers/server/trpc'
|
||||||
import { fetchSelectItems } from './fetchSelectItems'
|
import { fetchSelectItems } from './fetchSelectItems'
|
||||||
|
import { createCredentials } from './credentials/createCredentials'
|
||||||
|
import { deleteCredentials } from './credentials/deleteCredentials'
|
||||||
|
import { listCredentials } from './credentials/listCredentials'
|
||||||
|
|
||||||
export const integrationsRouter = router({
|
export const forgeRouter = router({
|
||||||
fetchSelectItems,
|
fetchSelectItems,
|
||||||
|
createCredentials,
|
||||||
|
listCredentials,
|
||||||
|
deleteCredentials,
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ export const ForgeSelectInput = ({
|
|||||||
return fetchers.find((fetcher) => fetcher.id === fetcherId)
|
return fetchers.find((fetcher) => fetcher.id === fetcherId)
|
||||||
}, [baseFetcher, blockDef.actions, fetcherId])
|
}, [baseFetcher, blockDef.actions, fetcherId])
|
||||||
|
|
||||||
const { data } = trpc.integrations.fetchSelectItems.useQuery(
|
const { data } = trpc.forge.fetchSelectItems.useQuery(
|
||||||
{
|
{
|
||||||
integrationId: blockDef.id,
|
integrationId: blockDef.id,
|
||||||
options: pick(options, [
|
options: pick(options, [
|
||||||
|
@ -34,14 +34,13 @@ export const ForgedCredentialsDropdown = ({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const { workspace, currentRole } = useWorkspace()
|
const { workspace, currentRole } = useWorkspace()
|
||||||
const { data, refetch, isLoading } =
|
const { data, refetch, isLoading } = trpc.forge.listCredentials.useQuery(
|
||||||
trpc.integrationCredentials.listCredentials.useQuery(
|
{
|
||||||
{
|
workspaceId: workspace?.id as string,
|
||||||
workspaceId: workspace?.id as string,
|
type: blockDef.id,
|
||||||
type: blockDef.id,
|
},
|
||||||
},
|
{ enabled: !!workspace?.id }
|
||||||
{ enabled: !!workspace?.id }
|
)
|
||||||
)
|
|
||||||
const [isDeleting, setIsDeleting] = useState<string>()
|
const [isDeleting, setIsDeleting] = useState<string>()
|
||||||
|
|
||||||
const { mutate } = trpc.credentials.deleteCredentials.useMutation({
|
const { mutate } = trpc.credentials.deleteCredentials.useMutation({
|
||||||
|
@ -42,7 +42,7 @@ export const ForgedCredentialsModal = ({
|
|||||||
listCredentials: { refetch: refetchCredentials },
|
listCredentials: { refetch: refetchCredentials },
|
||||||
},
|
},
|
||||||
} = trpc.useContext()
|
} = trpc.useContext()
|
||||||
const { mutate } = trpc.integrationCredentials.createCredentials.useMutation({
|
const { mutate } = trpc.forge.createCredentials.useMutation({
|
||||||
onMutate: () => setIsCreating(true),
|
onMutate: () => setIsCreating(true),
|
||||||
onSettled: () => setIsCreating(false),
|
onSettled: () => setIsCreating(false),
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
|
@ -100,12 +100,13 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: tokenInfoData } = trpc.whatsApp.getSystemTokenInfo.useQuery(
|
const { data: tokenInfoData } =
|
||||||
{
|
trpc.whatsAppInternal.getSystemTokenInfo.useQuery(
|
||||||
token: systemUserAccessToken,
|
{
|
||||||
},
|
token: systemUserAccessToken,
|
||||||
{ enabled: isNotEmpty(systemUserAccessToken) }
|
},
|
||||||
)
|
{ enabled: isNotEmpty(systemUserAccessToken) }
|
||||||
|
)
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setActiveStep(0)
|
setActiveStep(0)
|
||||||
@ -133,7 +134,7 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
setIsVerifying(true)
|
setIsVerifying(true)
|
||||||
try {
|
try {
|
||||||
const { expiresAt, scopes } =
|
const { expiresAt, scopes } =
|
||||||
await trpcVanilla.whatsApp.getSystemTokenInfo.query({
|
await trpcVanilla.whatsAppInternal.getSystemTokenInfo.query({
|
||||||
token: systemUserAccessToken,
|
token: systemUserAccessToken,
|
||||||
})
|
})
|
||||||
if (expiresAt !== 0) {
|
if (expiresAt !== 0) {
|
||||||
@ -167,16 +168,18 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
const isPhoneNumberAvailable = async () => {
|
const isPhoneNumberAvailable = async () => {
|
||||||
setIsVerifying(true)
|
setIsVerifying(true)
|
||||||
try {
|
try {
|
||||||
const { name } = await trpcVanilla.whatsApp.getPhoneNumber.query({
|
const { name } = await trpcVanilla.whatsAppInternal.getPhoneNumber.query({
|
||||||
systemToken: systemUserAccessToken,
|
systemToken: systemUserAccessToken,
|
||||||
phoneNumberId,
|
phoneNumberId,
|
||||||
})
|
})
|
||||||
setPhoneNumberName(name)
|
setPhoneNumberName(name)
|
||||||
try {
|
try {
|
||||||
const { message } =
|
const { message } =
|
||||||
await trpcVanilla.whatsApp.verifyIfPhoneNumberAvailable.query({
|
await trpcVanilla.whatsAppInternal.verifyIfPhoneNumberAvailable.query(
|
||||||
phoneNumberDisplayName: name,
|
{
|
||||||
})
|
phoneNumberDisplayName: name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (message === 'taken') {
|
if (message === 'taken') {
|
||||||
setIsVerifying(false)
|
setIsVerifying(false)
|
||||||
@ -186,7 +189,7 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const { verificationToken } =
|
const { verificationToken } =
|
||||||
await trpcVanilla.whatsApp.generateVerificationToken.mutate()
|
await trpcVanilla.whatsAppInternal.generateVerificationToken.mutate()
|
||||||
setVerificationToken(verificationToken)
|
setVerificationToken(verificationToken)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -56,14 +56,15 @@ export const WhatsAppModal = ({ isOpen, onClose }: ModalProps): JSX.Element => {
|
|||||||
|
|
||||||
const whatsAppSettings = typebot?.settings.whatsApp
|
const whatsAppSettings = typebot?.settings.whatsApp
|
||||||
|
|
||||||
const { data: phoneNumberData } = trpc.whatsApp.getPhoneNumber.useQuery(
|
const { data: phoneNumberData } =
|
||||||
{
|
trpc.whatsAppInternal.getPhoneNumber.useQuery(
|
||||||
credentialsId: typebot?.whatsAppCredentialsId as string,
|
{
|
||||||
},
|
credentialsId: typebot?.whatsAppCredentialsId as string,
|
||||||
{
|
},
|
||||||
enabled: !!typebot?.whatsAppCredentialsId,
|
{
|
||||||
}
|
enabled: !!typebot?.whatsAppCredentialsId,
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const toggleEnableWhatsApp = (isChecked: boolean) => {
|
const toggleEnableWhatsApp = (isChecked: boolean) => {
|
||||||
if (!phoneNumberData?.id || !typebot) return
|
if (!phoneNumberData?.id || !typebot) return
|
||||||
|
@ -42,22 +42,7 @@ export type FilePathUploadProps = z.infer<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export const generateUploadUrl = authenticatedProcedure
|
export const generateUploadUrl = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/generate-upload-url',
|
|
||||||
summary: 'Generate upload URL',
|
|
||||||
description: 'Generate the needed URL to upload a file from the client',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(inputSchema)
|
.input(inputSchema)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
presignedUrl: z.string(),
|
|
||||||
formData: z.record(z.string(), z.any()),
|
|
||||||
fileUrl: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mutation(async ({ input: { filePathProps, fileType }, ctx: { user } }) => {
|
.mutation(async ({ input: { filePathProps, fileType }, ctx: { user } }) => {
|
||||||
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
@ -1,23 +1,9 @@
|
|||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { z } from 'zod'
|
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
export const generateVerificationToken = authenticatedProcedure
|
export const generateVerificationToken = authenticatedProcedure.mutation(
|
||||||
.meta({
|
async () => {
|
||||||
openapi: {
|
|
||||||
method: 'POST',
|
|
||||||
path: '/verficiationTokens',
|
|
||||||
protect: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(z.void())
|
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
verificationToken: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mutation(async () => {
|
|
||||||
const oneHourLater = new Date(Date.now() + 1000 * 60 * 60)
|
const oneHourLater = new Date(Date.now() + 1000 * 60 * 60)
|
||||||
const verificationToken = await prisma.verificationToken.create({
|
const verificationToken = await prisma.verificationToken.create({
|
||||||
data: {
|
data: {
|
||||||
@ -28,4 +14,5 @@ export const generateVerificationToken = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
|
|
||||||
return { verificationToken: verificationToken.token }
|
return { verificationToken: verificationToken.token }
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
@ -13,20 +13,7 @@ const inputSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getPhoneNumber = authenticatedProcedure
|
export const getPhoneNumber = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/whatsapp/phoneNumber',
|
|
||||||
protect: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(inputSchema)
|
.input(inputSchema)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async ({ input, ctx: { user } }) => {
|
.query(async ({ input, ctx: { user } }) => {
|
||||||
const credentials = await getCredentials(user.id, input)
|
const credentials = await getCredentials(user.id, input)
|
||||||
if (!credentials)
|
if (!credentials)
|
||||||
|
@ -12,22 +12,7 @@ const inputSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getSystemTokenInfo = authenticatedProcedure
|
export const getSystemTokenInfo = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/whatsapp/systemToken',
|
|
||||||
protect: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(inputSchema)
|
.input(inputSchema)
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
appId: z.string(),
|
|
||||||
appName: z.string(),
|
|
||||||
expiresAt: z.number(),
|
|
||||||
scopes: z.array(z.string()),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async ({ input, ctx: { user } }) => {
|
.query(async ({ input, ctx: { user } }) => {
|
||||||
if (!input.token && !input.credentialsId)
|
if (!input.token && !input.credentialsId)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
@ -10,7 +10,7 @@ export const receiveMessagePreview = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/whatsapp/preview/webhook',
|
path: '/v1/whatsapp/preview/webhook',
|
||||||
summary: 'Message webhook',
|
summary: 'Message webhook',
|
||||||
tags: ['WhatsApp'],
|
tags: ['WhatsApp'],
|
||||||
},
|
},
|
||||||
|
@ -7,11 +7,14 @@ import { startWhatsAppPreview } from './startWhatsAppPreview'
|
|||||||
import { subscribePreviewWebhook } from './subscribePreviewWebhook'
|
import { subscribePreviewWebhook } from './subscribePreviewWebhook'
|
||||||
import { receiveMessagePreview } from './receiveMessagePreview'
|
import { receiveMessagePreview } from './receiveMessagePreview'
|
||||||
|
|
||||||
export const whatsAppRouter = router({
|
export const internalWhatsAppRouter = router({
|
||||||
getPhoneNumber,
|
getPhoneNumber,
|
||||||
getSystemTokenInfo,
|
getSystemTokenInfo,
|
||||||
verifyIfPhoneNumberAvailable,
|
verifyIfPhoneNumberAvailable,
|
||||||
generateVerificationToken,
|
generateVerificationToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const publicWhatsAppRouter = router({
|
||||||
startWhatsAppPreview,
|
startWhatsAppPreview,
|
||||||
subscribePreviewWebhook,
|
subscribePreviewWebhook,
|
||||||
receiveMessagePreview,
|
receiveMessagePreview,
|
||||||
|
@ -16,7 +16,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/typebots/{typebotId}/whatsapp/start-preview',
|
path: '/v1/typebots/{typebotId}/whatsapp/start-preview',
|
||||||
summary: 'Start preview',
|
summary: 'Start preview',
|
||||||
tags: ['WhatsApp'],
|
tags: ['WhatsApp'],
|
||||||
protect: true,
|
protect: true,
|
||||||
|
@ -7,7 +7,7 @@ export const subscribePreviewWebhook = publicProcedure
|
|||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/whatsapp/preview/webhook',
|
path: '/v1/whatsapp/preview/webhook',
|
||||||
summary: 'Subscribe webhook',
|
summary: 'Subscribe webhook',
|
||||||
tags: ['WhatsApp'],
|
tags: ['WhatsApp'],
|
||||||
},
|
},
|
||||||
|
@ -3,19 +3,7 @@ import { z } from 'zod'
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
|
|
||||||
export const verifyIfPhoneNumberAvailable = authenticatedProcedure
|
export const verifyIfPhoneNumberAvailable = authenticatedProcedure
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/whatsapp/phoneNumber/{phoneNumberDisplayName}/available',
|
|
||||||
protect: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.input(z.object({ phoneNumberDisplayName: z.string() }))
|
.input(z.object({ phoneNumberDisplayName: z.string() }))
|
||||||
.output(
|
|
||||||
z.object({
|
|
||||||
message: z.enum(['available', 'taken']),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.query(async ({ input: { phoneNumberDisplayName } }) => {
|
.query(async ({ input: { phoneNumberDisplayName } }) => {
|
||||||
const existingWhatsAppCredentials = await prisma.credentials.findFirst({
|
const existingWhatsAppCredentials = await prisma.credentials.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
@ -2,20 +2,18 @@ import { getAppVersionProcedure } from '@/features/dashboard/api/getAppVersionPr
|
|||||||
import { router } from '../trpc'
|
import { router } from '../trpc'
|
||||||
import { generateUploadUrl } from '@/features/upload/api/generateUploadUrl'
|
import { generateUploadUrl } from '@/features/upload/api/generateUploadUrl'
|
||||||
import { openAIRouter } from '@/features/blocks/integrations/openai/api/router'
|
import { openAIRouter } from '@/features/blocks/integrations/openai/api/router'
|
||||||
import { whatsAppRouter } from '@/features/whatsapp/router'
|
import { internalWhatsAppRouter } from '@/features/whatsapp/router'
|
||||||
import { zemanticAiRouter } from '@/features/blocks/integrations/zemanticAi/api/router'
|
import { zemanticAiRouter } from '@/features/blocks/integrations/zemanticAi/api/router'
|
||||||
import { forgedCredentialsRouter } from '@/features/forge/api/credentials/router'
|
import { forgeRouter } from '@/features/forge/api/router'
|
||||||
import { integrationsRouter } from '@/features/forge/api/router'
|
|
||||||
import { googleSheetsRouter } from '@/features/blocks/integrations/googleSheets/api/router'
|
import { googleSheetsRouter } from '@/features/blocks/integrations/googleSheets/api/router'
|
||||||
|
|
||||||
export const internalRouter = router({
|
export const internalRouter = router({
|
||||||
getAppVersionProcedure,
|
getAppVersionProcedure,
|
||||||
generateUploadUrl,
|
generateUploadUrl,
|
||||||
whatsApp: whatsAppRouter,
|
whatsAppInternal: internalWhatsAppRouter,
|
||||||
openAI: openAIRouter,
|
openAI: openAIRouter,
|
||||||
zemanticAI: zemanticAiRouter,
|
zemanticAI: zemanticAiRouter,
|
||||||
integrationCredentials: forgedCredentialsRouter,
|
forge: forgeRouter,
|
||||||
integrations: integrationsRouter,
|
|
||||||
sheets: googleSheetsRouter,
|
sheets: googleSheetsRouter,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import { analyticsRouter } from '@/features/analytics/api/router'
|
|||||||
import { collaboratorsRouter } from '@/features/collaboration/api/router'
|
import { collaboratorsRouter } from '@/features/collaboration/api/router'
|
||||||
import { customDomainsRouter } from '@/features/customDomains/api/router'
|
import { customDomainsRouter } from '@/features/customDomains/api/router'
|
||||||
import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent'
|
import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent'
|
||||||
|
import { publicWhatsAppRouter } from '@/features/whatsapp/router'
|
||||||
|
|
||||||
export const publicRouter = router({
|
export const publicRouter = router({
|
||||||
getLinkedTypebots,
|
getLinkedTypebots,
|
||||||
@ -25,6 +26,7 @@ export const publicRouter = router({
|
|||||||
collaborators: collaboratorsRouter,
|
collaborators: collaboratorsRouter,
|
||||||
customDomains: customDomainsRouter,
|
customDomains: customDomainsRouter,
|
||||||
processTelemetryEvent,
|
processTelemetryEvent,
|
||||||
|
whatsApp: publicWhatsAppRouter,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type PublicRouter = typeof publicRouter
|
export type PublicRouter = typeof publicRouter
|
||||||
|
@ -56965,6 +56965,551 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/v1/typebots/{typebotId}/whatsapp/start-preview": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "whatsApp-startWhatsAppPreview",
|
||||||
|
"summary": "Start preview",
|
||||||
|
"tags": [
|
||||||
|
"WhatsApp"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Authorization": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"to": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"startFrom": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"groupId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"event"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eventId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"eventId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"to"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "typebotId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/components/responses/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/whatsapp/preview/webhook": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "whatsApp-subscribePreviewWebhook",
|
||||||
|
"summary": "Subscribe webhook",
|
||||||
|
"tags": [
|
||||||
|
"WhatsApp"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "hub.challenge",
|
||||||
|
"in": "query",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hub.verify_token",
|
||||||
|
"in": "query",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/components/responses/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"operationId": "whatsApp-receiveMessagePreview",
|
||||||
|
"summary": "Message webhook",
|
||||||
|
"tags": [
|
||||||
|
"WhatsApp"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"entry": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"changes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"phone_number_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"phone_number_id"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"contacts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"profile": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"profile"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"body": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"body"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"text",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"button"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text",
|
||||||
|
"payload"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"button",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"interactive"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"interactive": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"button_reply": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"title"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"button_reply"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"interactive",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"image",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"video"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"video",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"audio"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"audio",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"document"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"from",
|
||||||
|
"type",
|
||||||
|
"document",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"changes"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"entry"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/components/responses/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
const urlRegex =
|
export const validateUrl = (url: string) => {
|
||||||
/^(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,})$/
|
try {
|
||||||
|
new URL(url)
|
||||||
export const validateUrl = (url: string) => urlRegex.test(url)
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -445,7 +445,9 @@ const parseReply =
|
|||||||
return block.options?.isRequired ?? defaultFileInputOptions.isRequired
|
return block.options?.isRequired ?? defaultFileInputOptions.isRequired
|
||||||
? { status: 'fail' }
|
? { status: 'fail' }
|
||||||
: { status: 'skip' }
|
: { status: 'skip' }
|
||||||
return { status: 'success', reply: inputValue }
|
const urls = inputValue.split(', ')
|
||||||
|
const status = urls.some((url) => validateUrl(url)) ? 'success' : 'fail'
|
||||||
|
return { status, reply: inputValue }
|
||||||
}
|
}
|
||||||
case InputBlockType.PAYMENT: {
|
case InputBlockType.PAYMENT: {
|
||||||
if (!inputValue) return { status: 'fail' }
|
if (!inputValue) return { status: 'fail' }
|
||||||
|
@ -140,11 +140,15 @@ const getIncomingMessageContent = async ({
|
|||||||
}
|
}
|
||||||
case 'document':
|
case 'document':
|
||||||
case 'audio':
|
case 'audio':
|
||||||
return
|
|
||||||
case 'video':
|
case 'video':
|
||||||
case 'image':
|
case 'image':
|
||||||
if (!typebotId) return
|
if (!typebotId) return
|
||||||
const mediaId = 'video' in message ? message.video.id : message.image.id
|
let mediaId: string | undefined
|
||||||
|
if (message.type === 'video') mediaId = message.video.id
|
||||||
|
if (message.type === 'image') mediaId = message.image.id
|
||||||
|
if (message.type === 'audio') mediaId = message.audio.id
|
||||||
|
if (message.type === 'document') mediaId = message.document.id
|
||||||
|
if (!mediaId) return
|
||||||
return (
|
return (
|
||||||
env.NEXTAUTH_URL +
|
env.NEXTAUTH_URL +
|
||||||
`/api/typebots/${typebotId}/whatsapp/media/${mediaId}`
|
`/api/typebots/${typebotId}/whatsapp/media/${mediaId}`
|
||||||
|
Reference in New Issue
Block a user