chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { createChannelWrapper } from '@botpress/common'
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import { GoogleClient, wrapWithTryCatch } from '../google-api'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const wrapChannel: typeof _injectTools = (meta, channelImpl) =>
|
||||
_injectTools(meta, (props) =>
|
||||
wrapWithTryCatch(() => {
|
||||
props.logger
|
||||
.forBot()
|
||||
.debug(
|
||||
`Sending message of type "${meta.messageType}" on channel "${meta.channelName}" for bot "${props.ctx.botId}"`
|
||||
)
|
||||
return channelImpl(props as Parameters<typeof channelImpl>[0])
|
||||
}, `Unable to send message of type "${meta.messageType}" on channel "${meta.channelName}"`)()
|
||||
)
|
||||
|
||||
const _injectTools = createChannelWrapper<bp.IntegrationProps>()({
|
||||
toolFactories: {
|
||||
async googleClient({ client, ctx }) {
|
||||
return await GoogleClient.create({ client, ctx })
|
||||
},
|
||||
|
||||
async inReplyTo({ client, conversation }) {
|
||||
const { state } = await client.getState({
|
||||
type: 'conversation',
|
||||
name: 'thread',
|
||||
id: conversation.id,
|
||||
})
|
||||
|
||||
if (!state.payload.inReplyTo) {
|
||||
throw new sdk.RuntimeError('Attempting to reply to a message, but no inReplyTo tag was found')
|
||||
}
|
||||
|
||||
return state.payload.inReplyTo
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,187 @@
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import { RuntimeError } from '@botpress/sdk'
|
||||
import { GoogleClient } from '../google-api'
|
||||
import {
|
||||
composeRawEmail,
|
||||
generateAudioMessage,
|
||||
generateCardMessage,
|
||||
generateCarouselMessage,
|
||||
generateFileDownloadMessage,
|
||||
generateImageMessage,
|
||||
generateLocationMessage,
|
||||
generateMarkdownMessage,
|
||||
generateVideoMessage,
|
||||
} from '../utils/mail-composing'
|
||||
import { wrapChannel } from './channel-wrapper'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const channels = {
|
||||
channel: {
|
||||
messages: {
|
||||
image: wrapChannel({ channelName: 'channel', messageType: 'image' }, (props) => {
|
||||
const { imageUrl, title: altText } = props.payload
|
||||
const htmlContent = generateImageMessage({ imageUrl, altText: altText ?? 'image' })
|
||||
const textContent = `Image:\n${imageUrl}`
|
||||
|
||||
return _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
audio: wrapChannel({ channelName: 'channel', messageType: 'audio' }, (props) => {
|
||||
const { audioUrl, title } = props.payload
|
||||
const htmlContent = generateAudioMessage({ audioUrl, title: title ?? 'Play audio file' })
|
||||
const textContent = `Audio file:\n${audioUrl}`
|
||||
|
||||
return _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
video: wrapChannel({ channelName: 'channel', messageType: 'video' }, (props) => {
|
||||
const { videoUrl, title } = props.payload
|
||||
const htmlContent = generateVideoMessage({ videoUrl, title: title ?? 'Play video file' })
|
||||
const textContent = `Video file:\n${videoUrl}`
|
||||
|
||||
return _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
file: wrapChannel({ channelName: 'channel', messageType: 'file' }, (props) => {
|
||||
const { fileUrl, title } = props.payload
|
||||
const htmlContent = generateFileDownloadMessage({ fileUrl, title: title ?? 'Download file' })
|
||||
const textContent = `Linked file:\n${fileUrl}`
|
||||
|
||||
return _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
text: wrapChannel({ channelName: 'channel', messageType: 'text' }, async (props) => {
|
||||
const { text: textContent } = props.payload
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
})
|
||||
}),
|
||||
choice: wrapChannel({ channelName: 'channel', messageType: 'choice' }, async (props) => {
|
||||
const { text, options } = props.payload
|
||||
let content = `${text}\n`
|
||||
|
||||
for (const option of options) {
|
||||
content += `- ${option.label}\n`
|
||||
}
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent: content,
|
||||
})
|
||||
}),
|
||||
markdown: wrapChannel({ channelName: 'channel', messageType: 'markdown' }, async (props) => {
|
||||
const { markdown } = props.payload
|
||||
const htmlContent = generateMarkdownMessage({ markdown })
|
||||
const textContent = markdown
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
location: wrapChannel({ channelName: 'channel', messageType: 'location' }, async (props) => {
|
||||
const { latitude, longitude, address, title } = props.payload
|
||||
const htmlContent = generateLocationMessage({ latitude, longitude, address, title })
|
||||
const textContent = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
card: wrapChannel({ channelName: 'channel', messageType: 'card' }, async (props) => {
|
||||
const { title, subtitle, imageUrl, actions } = props.payload
|
||||
|
||||
const htmlContent = generateCardMessage({ title, subtitle: subtitle ?? '', imageUrl: imageUrl ?? '', actions })
|
||||
const textContent = `${title}\n${subtitle}\n\n${actions.map((a) => `${a.label}: ${a.value}`).join('\n')}`
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
carousel: wrapChannel({ channelName: 'channel', messageType: 'carousel' }, async (props) => {
|
||||
const { items: cards } = props.payload
|
||||
|
||||
const htmlContent = generateCarouselMessage({
|
||||
cards: cards.map((c) => ({ ...c, subtitle: c.subtitle ?? '', imageUrl: c.imageUrl ?? '' })),
|
||||
})
|
||||
const textContent = cards
|
||||
.map((c) => `${c.title}\n${c.subtitle}\n\n${c.actions.map((a) => `${a.label}: ${a.value}`).join('\n')}`)
|
||||
.join('\n\n\n')
|
||||
|
||||
await _sendEmailReply({
|
||||
...props,
|
||||
textContent,
|
||||
htmlContent,
|
||||
})
|
||||
}),
|
||||
dropdown: wrapChannel({ channelName: 'channel', messageType: 'dropdown' }, () => {
|
||||
throw new sdk.RuntimeError('This message type is not yet implemented')
|
||||
}),
|
||||
bloc: wrapChannel({ channelName: 'channel', messageType: 'bloc' }, () => {
|
||||
throw new sdk.RuntimeError('This message type is not yet implemented')
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as const satisfies bp.IntegrationProps['channels']
|
||||
|
||||
const _sendEmailReply = async ({
|
||||
conversation,
|
||||
ack,
|
||||
textContent,
|
||||
htmlContent,
|
||||
inReplyTo,
|
||||
googleClient,
|
||||
}: bp.AnyMessageProps & {
|
||||
logger: bp.Logger
|
||||
textContent: string
|
||||
htmlContent?: string
|
||||
inReplyTo: string
|
||||
googleClient: GoogleClient
|
||||
}) => {
|
||||
const { threadId, email, subject, references, cc } = _getConversationInfo(conversation)
|
||||
|
||||
const raw = await composeRawEmail({
|
||||
to: email,
|
||||
subject,
|
||||
text: textContent,
|
||||
html: htmlContent ?? textContent,
|
||||
textEncoding: 'base64',
|
||||
inReplyTo,
|
||||
references: references ?? inReplyTo,
|
||||
cc,
|
||||
})
|
||||
|
||||
const res = await googleClient.messages.send(raw, threadId)
|
||||
|
||||
await ack({ tags: { id: `${res.id}` } })
|
||||
}
|
||||
|
||||
const _getConversationInfo = (conversation: bp.AnyMessageProps['conversation']) => {
|
||||
const { id, tags } = conversation
|
||||
const { id: threadId, subject, email, references, cc } = tags
|
||||
|
||||
if (!(threadId && subject && email)) {
|
||||
throw new RuntimeError(`No valid information found for conversation ${id}`)
|
||||
}
|
||||
|
||||
return { threadId, subject, email, references, cc }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './channels'
|
||||
Reference in New Issue
Block a user