Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/prod-master-version-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
run: pnpm bp login -y --token ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} --workspace-id ${{ secrets.PRODUCTION_CLOUD_OPS_WORKSPACE_ID }}
- name: Check integration versions
run: |
SKIP_INTEGRATIONS=("docusign" "zendesk-messaging-hitl")
SKIP_INTEGRATIONS=("docusign" "zendesk" "zendesk-messaging-hitl")

integrations=$(ls -d integrations/*/ | xargs -n1 basename | sort -u)
should_fail=0
Expand Down
2 changes: 1 addition & 1 deletion integrations/asana/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { configuration, states, user, channels, actions } from './src/definition

export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: '0.3.10',
version: '0.3.11',
title: 'Asana',
readme: 'hub.md',
description: 'Connect your bot to your Asana inbox, create and update tasks, add comments, and locate users.',
Expand Down
4 changes: 2 additions & 2 deletions integrations/asana/src/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export { channels } from './channels'

export const configuration = {
schema: z.object({
apiToken: z.string().min(1).describe('API Token'),
workspaceGid: z.string().min(1).describe('Workspace Global ID'),
apiToken: z.string().min(1).title('API Token').describe('API Token'),
workspaceGid: z.string().min(1).title('Workspace Global ID').describe('Workspace Global ID'),
}),
} satisfies IntegrationDefinitionProps['configuration']

Expand Down
2 changes: 1 addition & 1 deletion integrations/bigcommerce-sync/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { configuration, states, actions } from './src/definitions/index'
export default new IntegrationDefinition({
name: 'bigcommerce',
title: 'BigCommerce',
version: '3.2.1',
version: '3.2.2',
readme: 'hub.md',
icon: 'icon.svg',
description: 'Sync products from BigCommerce to Botpress',
Expand Down
12 changes: 2 additions & 10 deletions integrations/bigcommerce-sync/src/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@ import { z, IntegrationDefinitionProps } from '@botpress/sdk'

export const configuration = {
schema: z.object({
storeHash: z
.string({
description: 'Your BigCommerce store hash (e.g., abc123)',
})
.min(1),
accessToken: z
.string({
description: 'BigCommerce API Access Token',
})
.min(1),
storeHash: z.string().min(1).title('Store Hash').describe('Your BigCommerce store hash (e.g., abc123)'),
accessToken: z.string().min(1).title('Access Token').describe('BigCommerce API Access Token'),
}),
} satisfies IntegrationDefinitionProps['configuration']

Expand Down
4 changes: 2 additions & 2 deletions integrations/dalle/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ const modelDescription = 'Model to use for image generation. Defaults to "dall-e

export default new IntegrationDefinition({
name: 'dalle',
version: '0.3.5',
version: '0.3.6',
icon: 'icon.svg',
title: 'DALL-E (Deprecated)',
description: 'Integrate DALL-E to generate images directly within your chatbot conversations.',
readme: 'hub.md',
configuration: {
schema: z.object({
apiKey: z.string().describe('Open AI Key'),
apiKey: z.string().title('OpenAI API Key').describe('An API Key for the OpenAI DALL-E service'),
}),
},
channels: {},
Expand Down
15 changes: 11 additions & 4 deletions integrations/email/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const emailSchema = z.object({

export default new IntegrationDefinition({
name: 'email',
version: '0.1.1',
version: '0.1.2',
title: 'Email',
description: 'Send and receive emails using IMAP and SMTP protocols',
readme: 'hub.md',
Expand All @@ -21,10 +21,17 @@ export default new IntegrationDefinition({
.object({
user: z
.string()
.title('Email Address')
.describe('The email account you want to use to receive and send messages. Example: example@gmail.com'),
password: z.string().describe('The password to the email account.'),
imapHost: z.string().describe('The imap server you want to connect to. Example: imap.gmail.com'),
smtpHost: z.string().describe('The smtp server you want to connect to. Example: smtp.gmail.com'),
password: z.string().title('Account Password').describe('The password to the email account.'),
imapHost: z
.string()
.title('IMAP Host')
.describe('The imap server you want to connect to. Example: imap.gmail.com'),
smtpHost: z
.string()
.title('SMTP Host')
.describe('The smtp server you want to connect to. Example: smtp.gmail.com'),
})
.required(),
},
Expand Down
4 changes: 2 additions & 2 deletions integrations/make/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const INTEGRATION_NAME = 'make'

export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: '0.3.6',
version: '0.3.7',
title: 'Make.com (Deprecated)',
icon: 'icon.svg',
description:
Expand All @@ -13,7 +13,7 @@ export default new IntegrationDefinition({
configuration: {
schema: z
.object({
webhookUrl: z.string().url().describe('Make.com webhook URL'),
webhookUrl: z.string().url().title('Webhook URL').describe('Make.com webhook URL'),
})
.describe('Configuration schema for Make.com Integration'),
},
Expand Down
4 changes: 3 additions & 1 deletion integrations/webhook/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { sentry as sentryHelpers } from '@botpress/sdk-addons'

export default new IntegrationDefinition({
name: 'webhook',
version: '1.1.2',
version: '1.1.3',
title: 'Webhook',
description: 'Use webhooks to send and receive data from external systems and trigger workflows.',
icon: 'icon.svg',
Expand All @@ -13,12 +13,14 @@ export default new IntegrationDefinition({
secret: z
.string()
.optional()
.title('Webhook Header Secret')
.describe(
'Secret that must be sent with the request as a header called "x-bp-secret." Leave empty to allow all requests without a secret.'
),
allowedOrigins: z
.array(z.string())
.optional()
.title('Allowed Origins')
.describe(
'List of allowed origins for CORS. Leaving this field empty will block all requests originating from a browser and only allow requests from a server.'
),
Expand Down
7 changes: 6 additions & 1 deletion integrations/whatsapp/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const defaultBotPhoneNumberId = {
}

export const INTEGRATION_NAME = 'whatsapp'
export const INTEGRATION_VERSION = '4.7.1'
export const INTEGRATION_VERSION = '4.8.0'
export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: INTEGRATION_VERSION,
Expand Down Expand Up @@ -323,6 +323,11 @@ export default new IntegrationDefinition({
},
},
events: {
messageRead: {
title: 'Message Read',
description: 'Triggered when a user reads a message',
schema: z.object({}),
},
reactionAdded: {
title: 'Reaction Added',
description: 'Triggered when a user adds a reaction to a message',
Expand Down
15 changes: 6 additions & 9 deletions integrations/whatsapp/src/actions/start-conversation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { isApiError } from '@botpress/client'
import { posthogHelper } from '@botpress/common'
import { INTEGRATION_NAME, INTEGRATION_VERSION } from 'integration.definition'
import { BodyComponent, BodyParameter, Language, Template } from 'whatsapp-api-js/messages'
import { getDefaultBotPhoneNumberId, getAuthenticatedWhatsappClient } from '../auth'
import { formatPhoneNumber } from '../misc/phone-number-to-whatsapp'
import { safeFormatPhoneNumber } from '../misc/phone-number-to-whatsapp'
import { getTemplateText, parseTemplateVariablesJSON } from '../misc/template-utils'
import { TemplateVariables } from '../misc/types'
import { hasAtleastOne, logForBotAndThrow } from '../misc/util'
Expand Down Expand Up @@ -40,11 +39,9 @@ export const startConversation: bp.IntegrationProps['actions']['startConversatio
templateVariables = parseTemplateVariablesJSON(templateVariablesJson, logger)
}

let formattedUserPhone = userPhone
try {
formattedUserPhone = formatPhoneNumber(userPhone)
} catch (thrown) {
const distinctId = isApiError(thrown) ? thrown.id : undefined
const formatPhoneNumberResponse = safeFormatPhoneNumber(userPhone)
if (formatPhoneNumberResponse.success === false) {
const distinctId = formatPhoneNumberResponse.error.id
await posthogHelper.sendPosthogEvent(
{
distinctId: distinctId ?? 'no id',
Expand All @@ -56,15 +53,15 @@ export const startConversation: bp.IntegrationProps['actions']['startConversatio
},
{ integrationName: INTEGRATION_NAME, integrationVersion: INTEGRATION_VERSION, key: bp.secrets.POSTHOG_KEY }
)
const errorMessage = (thrown instanceof Error ? thrown : new Error(String(thrown))).message
const errorMessage = formatPhoneNumberResponse.error.message
logForBotAndThrow(`Failed to parse phone number "${userPhone}": ${errorMessage}`, logger)
}

const { conversation } = await client.getOrCreateConversation({
channel: 'channel',
tags: {
botPhoneNumberId,
userPhone: formattedUserPhone,
userPhone: formatPhoneNumberResponse.phoneNumber,
},
})

Expand Down
28 changes: 27 additions & 1 deletion integrations/whatsapp/src/misc/phone-number-to-whatsapp.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import { RuntimeError } from '@botpress/client'
import { ApiError, isApiError, RuntimeError } from '@botpress/client'
import { parsePhoneNumber, ParsedPhoneNumber } from 'awesome-phonenumber'

const ARGENTINA_COUNTRY_CODE = 54
const ARGENTINA_COUNTRY_CODE_AFTER_PREFIX = 9
const MEXICO_COUNTRY_CODE = 52
const MEXICO_COUNTRY_CODE_AFTER_PREFIX = 1

type formatPhoneNumberResponse =
| {
success: true
phoneNumber: string
}
| {
success: false
error: ApiError
}

export function safeFormatPhoneNumber(phoneNumber: string): formatPhoneNumberResponse {
try {
const formattedPhoneNumber = formatPhoneNumber(phoneNumber)
return {
success: true,
phoneNumber: formattedPhoneNumber,
}
} catch (thrown) {
const error = isApiError(thrown) ? thrown : new RuntimeError(String(thrown))
return {
success: false,
error,
}
}
}

export function formatPhoneNumber(phoneNumber: string) {
if (!phoneNumber.startsWith('+')) {
// TODO log to use international phone number
Expand Down
16 changes: 7 additions & 9 deletions integrations/whatsapp/src/webhook/handlers/messages.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { RuntimeError, isApiError } from '@botpress/client'
import { RuntimeError } from '@botpress/client'
import { posthogHelper } from '@botpress/common'
import { ValueOf } from '@botpress/sdk/dist/utils/type-utils'
import axios from 'axios'
import { INTEGRATION_NAME, INTEGRATION_VERSION } from 'integration.definition'
import { getAccessToken, getAuthenticatedWhatsappClient } from '../../auth'
import { formatPhoneNumber } from '../../misc/phone-number-to-whatsapp'
import { safeFormatPhoneNumber } from '../../misc/phone-number-to-whatsapp'
import { WhatsAppMessage, WhatsAppMessageValue } from '../../misc/types'
import { getMessageFromWhatsappMessageId } from '../../misc/util'
import { getMediaInfos } from '../../misc/whatsapp-utils'
Expand Down Expand Up @@ -39,11 +39,9 @@ async function _handleIncomingMessage(
client: bp.Client,
logger: bp.Logger
) {
let userPhone = message.from
try {
userPhone = formatPhoneNumber(message.from)
} catch (thrown) {
const distinctId = isApiError(thrown) ? thrown.id : undefined
const formatPhoneNumberResponse = safeFormatPhoneNumber(message.from)
if (formatPhoneNumberResponse.success === false) {
const distinctId = formatPhoneNumberResponse.error.id
await posthogHelper.sendPosthogEvent(
{
distinctId: distinctId ?? 'no id',
Expand All @@ -55,14 +53,14 @@ async function _handleIncomingMessage(
},
{ integrationName: INTEGRATION_NAME, integrationVersion: INTEGRATION_VERSION, key: bp.secrets.POSTHOG_KEY }
)
const errorMessage = thrown instanceof Error ? thrown.message : String(thrown)
const errorMessage = formatPhoneNumberResponse.error.message
logger.error(`Failed to parse phone number "${message.from}": ${errorMessage}`)
}

const { conversation } = await client.getOrCreateConversation({
channel: 'channel',
tags: {
userPhone,
userPhone: formatPhoneNumberResponse.success ? formatPhoneNumberResponse.phoneNumber : message.from,
botPhoneNumberId: value.metadata.phone_number_id,
},
})
Expand Down
18 changes: 17 additions & 1 deletion integrations/whatsapp/src/webhook/handlers/status.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { getMessageFromWhatsappMessageId } from 'src/misc/util'
import { WhatsAppStatusValue } from '../../misc/types'
import * as bp from '.botpress'

export const statusHandler = async (value: WhatsAppStatusValue, props: bp.HandlerProps) => {
const { logger } = props
const { client, logger } = props

if (value.status === 'read') {
const message = await getMessageFromWhatsappMessageId(value.id, client)
if (!message) {
logger.forBot().error(`No message found for WhatsApp message ID ${value.id}, cannot create messageRead event`)
return
}

await client.createEvent({
type: 'messageRead',
conversationId: message.conversationId,
messageId: message.id,
payload: {},
})
}

if (value.status === 'failed') {
const errorDetails =
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/linter/rulesets/bot.ruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export const BOT_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'error',
given: '$.configuration..schema.properties[*].x-zui',
given: '$.configuration..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) => `configuration parameter "${path.at(isFallback ? -5 : -3)}"`,
fallbackExtractor: titleFallbackExtractor,
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/linter/rulesets/integration.ruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ export const INTEGRATION_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'warn',
given: '$.actions[*].output..schema.properties[*].x-zui',
given: '$.actions[*].output..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) =>
`output parameter "${path.at(isFallback ? -5 : -3)}" of action "${path[1]}"`,
Expand Down Expand Up @@ -174,10 +174,10 @@ export const INTEGRATION_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'error',
given: '$.configuration..schema.properties[*].x-zui',
given: '$.configuration..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) => `configuration parameter "${path.at(isFallback ? -5 : -3)}"`,
fallbackExtractor: titleFallbackExtractor,
Expand Down Expand Up @@ -220,10 +220,10 @@ export const INTEGRATION_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'error',
given: '$.configurations[*]..schema.properties[*].x-zui',
given: '$.configurations[*]..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) =>
`configuration field "${path.at(isFallback ? -5 : -3)}" of configuration "${path[1]}"`,
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/linter/rulesets/interface.ruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export const INTERFACE_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'warn',
given: '$.actions[*].input..schema.properties[*].x-zui',
given: '$.actions[*].input..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) =>
`input parameter "${path.at(isFallback ? -5 : -3)}" of action "${path[1]}"`,
Expand Down Expand Up @@ -44,10 +44,10 @@ export const INTERFACE_RULESET = preprocessRuleset({
message:
'{{description}}: {{error}} {{callToAction}} provide a non-empty title by using .title() in its Zod schema',
severity: 'warn',
given: '$.actions[*].output..schema.properties[*].x-zui',
given: '$.actions[*].output..schema.properties[*]',
then: [
{
field: 'title',
field: 'x-zui.title',
function: truthyWithMessage({
failMsgMapper: ({ path, isFallback }) =>
`output parameter "${path.at(isFallback ? -5 : -3)}" of action "${path[1]}"`,
Expand Down
Loading