chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { RuntimeError } from '@botpress/client'
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import * as common from '@botpress/sdk-addons'
|
||||
import * as formatter from './payloadFormatter'
|
||||
import * as vonage from './vonage'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
const integration = new bp.Integration({
|
||||
register: async () => {},
|
||||
unregister: async () => {},
|
||||
actions: {
|
||||
async startConversation(props) {
|
||||
const vonageChannel = props.input.conversation.channel
|
||||
const channelId = props.input.conversation.channelId
|
||||
const userId = props.input.conversation.userId
|
||||
|
||||
if (!(vonageChannel && channelId && userId)) {
|
||||
throw new sdk.RuntimeError('Could not create conversation: missing channel, channelId or userId')
|
||||
}
|
||||
|
||||
const { conversation } = await props.client.getOrCreateConversation({
|
||||
tags: {
|
||||
channel: vonageChannel,
|
||||
channelId,
|
||||
userId,
|
||||
},
|
||||
channel: 'channel',
|
||||
})
|
||||
|
||||
return {
|
||||
conversationId: conversation.id,
|
||||
}
|
||||
},
|
||||
async getOrCreateUser(props) {
|
||||
const vonageChannel = props.input.user.channel
|
||||
const userId = props.input.user.userId
|
||||
if (!(vonageChannel && userId)) {
|
||||
throw new sdk.RuntimeError('Could not create a user: missing channel or userId')
|
||||
}
|
||||
|
||||
const { user } = await props.client.getOrCreateUser({
|
||||
tags: {
|
||||
channel: vonageChannel,
|
||||
userId,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
}
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
channel: {
|
||||
messages: {
|
||||
text: async (props) => {
|
||||
const payload = { message_type: 'text', text: props.payload.text }
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
image: async (props) => {
|
||||
const payload = { message_type: 'image', image: { url: props.payload.imageUrl } }
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
audio: async (props) => {
|
||||
const payload = { message_type: 'audio', audio: { url: props.payload.audioUrl } }
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
video: async (props) => {
|
||||
const payload = { message_type: 'video', video: { url: props.payload.videoUrl } }
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
file: async (props) => {
|
||||
const payload = { message_type: 'file', file: { url: props.payload.fileUrl } }
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
location: async (props) => {
|
||||
const payload = formatter.formatLocationPayload(props.payload)
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
carousel: async (props) => {
|
||||
const payloads = formatter.formatCarouselPayload(props.payload)
|
||||
for (const payload of payloads) {
|
||||
await vonage.sendMessage(props, payload)
|
||||
}
|
||||
},
|
||||
card: async (props) => {
|
||||
const payload = formatter.formatCardPayload(props.payload)
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
dropdown: async (props) => {
|
||||
const payload = formatter.formatDropdownPayload(props.payload)
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
choice: async (props) => {
|
||||
const payload = formatter.formatChoicePayload(props.payload)
|
||||
await vonage.sendMessage(props, payload)
|
||||
},
|
||||
bloc: () => {
|
||||
throw new RuntimeError('Not implemented')
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: async ({ req, client }) => {
|
||||
if (!req.body) {
|
||||
console.warn('Handler received an empty body')
|
||||
return
|
||||
}
|
||||
|
||||
const data = JSON.parse(req.body)
|
||||
|
||||
console.info(`Handler received request of type ${data.message_type}`)
|
||||
|
||||
if (data.message_type !== 'text') {
|
||||
throw new Error('Handler received an invalid message type')
|
||||
}
|
||||
|
||||
const { conversation } = await client.getOrCreateConversation({
|
||||
channel: 'channel',
|
||||
tags: {
|
||||
channel: data.channel,
|
||||
channelId: data.to,
|
||||
userId: data.from,
|
||||
},
|
||||
})
|
||||
|
||||
const { user } = await client.getOrCreateUser({
|
||||
tags: {
|
||||
channel: data.channel,
|
||||
userId: data.from,
|
||||
},
|
||||
})
|
||||
|
||||
await client.createMessage({
|
||||
tags: { id: data.message_uuid },
|
||||
type: 'text',
|
||||
userId: user.id,
|
||||
conversationId: conversation.id,
|
||||
payload: { text: data.text },
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export default common.reporting.wrapIntegration(integration)
|
||||
@@ -0,0 +1,133 @@
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export function formatLocationPayload(payload: bp.channels.channel.location.Location) {
|
||||
return {
|
||||
message_type: 'custom',
|
||||
custom: {
|
||||
type: 'location',
|
||||
location: {
|
||||
latitude: payload.latitude,
|
||||
longitude: payload.longitude,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDropdownPayload(payload: bp.channels.channel.dropdown.Dropdown) {
|
||||
return {
|
||||
message_type: 'custom',
|
||||
custom: {
|
||||
type: 'interactive',
|
||||
interactive: {
|
||||
type: 'list',
|
||||
body: {
|
||||
text: payload.text,
|
||||
},
|
||||
action: {
|
||||
button: 'Select an option',
|
||||
sections: [
|
||||
{
|
||||
rows: payload.options.map((x, i) => ({ id: `slot-${i}::${x.value}`, title: x.label })),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function formatChoicePayload(payload: bp.channels.channel.choice.Choice) {
|
||||
if (payload.options.length < 3) {
|
||||
return {
|
||||
message_type: 'custom',
|
||||
custom: {
|
||||
type: 'interactive',
|
||||
interactive: {
|
||||
type: 'button',
|
||||
body: {
|
||||
text: payload.text,
|
||||
},
|
||||
action: {
|
||||
buttons: payload.options.map((x, i) => ({
|
||||
type: 'reply',
|
||||
reply: { id: `slot-${i}::${x.value}`, title: x.label },
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.options.length <= 10) {
|
||||
return {
|
||||
message_type: 'custom',
|
||||
custom: {
|
||||
type: 'interactive',
|
||||
interactive: {
|
||||
type: 'list',
|
||||
body: {
|
||||
text: payload.text,
|
||||
},
|
||||
action: {
|
||||
button: 'Select an option',
|
||||
sections: [
|
||||
{
|
||||
rows: payload.options.map((x, i) => ({ id: `slot-${i}::${x.value}`, title: x.label })),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message_type: 'text',
|
||||
text: `${payload.text}\n\n${payload.options.map(({ label }, idx) => `*(${idx + 1})* ${label}`).join('\n')}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function formatCarouselPayload(payload: bp.channels.channel.carousel.Carousel) {
|
||||
let count = 0
|
||||
return payload.items.map((card) => {
|
||||
const cardPayload = formatCardPayload(card, count)
|
||||
count += card.actions.length
|
||||
return cardPayload
|
||||
})
|
||||
}
|
||||
|
||||
type CardOption = CardSay | CardPostback | CardUrl
|
||||
|
||||
type CardSay = { title: string; type: 'say'; value: string }
|
||||
type CardPostback = { title: string; type: 'postback'; value: string }
|
||||
type CardUrl = { title: string; type: 'url' }
|
||||
|
||||
export function formatCardPayload(payload: bp.channels.channel.card.Card, count: number = 0) {
|
||||
const options: CardOption[] = []
|
||||
|
||||
payload.actions.forEach((action) => {
|
||||
if (action.action === 'say') {
|
||||
options.push({ title: action.label, type: 'say', value: action.value })
|
||||
} else if (action.action === 'url') {
|
||||
options.push({ title: `${action.label} : ${action.value}`, type: 'url' })
|
||||
} else if (action.action === 'postback') {
|
||||
options.push({ title: action.label, type: 'postback', value: action.value })
|
||||
}
|
||||
})
|
||||
|
||||
const body = `*${payload.title}*\n\n${payload.subtitle ? `${payload.subtitle}\n\n` : ''}${options
|
||||
.map(({ title }, idx) => `*(${idx + count + 1})* ${title}`)
|
||||
.join('\n')}`
|
||||
|
||||
if (payload.imageUrl) {
|
||||
return {
|
||||
message_type: 'image',
|
||||
image: {
|
||||
url: payload.imageUrl,
|
||||
caption: body,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return { message_type: 'text', text: body }
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import axios from 'axios'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
function getRequestMetadata(conversation: SendMessageProps['conversation']) {
|
||||
const channel = conversation.tags?.channel
|
||||
const channelId = conversation.tags?.channelId
|
||||
const userId = conversation.tags?.userId
|
||||
|
||||
if (!channelId) {
|
||||
throw new Error('Invalid channel id')
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Error('Invalid user id')
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
throw new Error('Invalid channel')
|
||||
}
|
||||
|
||||
return { to: userId, from: channelId, channel }
|
||||
}
|
||||
|
||||
type SendMessageProps = Pick<bp.AnyMessageProps, 'ctx' | 'conversation' | 'ack'>
|
||||
export async function sendMessage({ conversation, ctx, ack }: SendMessageProps, payload: any) {
|
||||
const { to, from, channel } = getRequestMetadata(conversation)
|
||||
const response = await axios.post(
|
||||
'https://api.nexmo.com/v1/messages',
|
||||
{
|
||||
...payload,
|
||||
from,
|
||||
to,
|
||||
channel,
|
||||
},
|
||||
{
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
auth: { username: ctx.configuration.apiKey, password: ctx.configuration.apiSecret },
|
||||
}
|
||||
)
|
||||
await ack({ tags: { id: response.data.message_uuid } })
|
||||
}
|
||||
Reference in New Issue
Block a user