chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import rootConfig from '../../eslint.config.mjs'
|
||||
|
||||
export default [
|
||||
...rootConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
> The Botpress app for Zapier is currently in BETA. In order to use Botpress in your Zaps please [click here to obtain access](https://zapier.com/developer/public-invite/179950/04ff1359293c7c382bbfe0806b1212f7/) to the private beta in Zapier.
|
||||
|
||||
---
|
||||
|
||||
The Zapier integration enables seamless integration between your AI-powered chatbot and Zapier, a powerful automation platform. Connect your chatbot to Zapier and unlock endless possibilities for automating workflows and integrating with various third-party apps and services.
|
||||
|
||||
With this integration, you can easily create custom workflows, automate tasks, and streamline data transfers between your chatbot and other apps. Leverage Zapier's extensive library of supported apps to connect your chatbot with CRMs, email marketing platforms, project management tools, and much more.
|
||||
|
||||
Simplify your workflows and enhance the capabilities of your chatbot with the Zapier Integration for Botpress.
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M128.080089,-0.000183105 C135.311053,0.0131003068 142.422517,0.624138494 149.335663,1.77979593 L149.335663,1.77979593 L149.335663,76.2997796 L202.166953,23.6044907 C208.002065,27.7488446 213.460883,32.3582023 218.507811,37.3926715 C223.557281,42.4271407 228.192318,47.8867213 232.346817,53.7047992 L232.346817,53.7047992 L179.512985,106.400063 L254.227854,106.400063 C255.387249,113.29414 256,120.36111 256,127.587243 L256,127.587243 L256,127.759881 C256,134.986013 255.387249,142.066204 254.227854,148.960282 L254.227854,148.960282 L179.500273,148.960282 L232.346817,201.642324 C228.192318,207.460402 223.557281,212.919983 218.523066,217.954452 L218.523066,217.954452 L218.507811,217.954452 C213.460883,222.988921 208.002065,227.6115 202.182208,231.742607 L202.182208,231.742607 L149.335663,179.04709 L149.335663,253.5672 C142.435229,254.723036 135.323765,255.333244 128.092802,255.348499 L128.092802,255.348499 L127.907197,255.348499 C120.673691,255.333244 113.590195,254.723036 106.677048,253.5672 L106.677048,253.5672 L106.677048,179.04709 L53.8457596,231.742607 C42.1780766,223.466917 31.977435,213.278734 23.6658953,201.642324 L23.6658953,201.642324 L76.4997269,148.960282 L1.78485803,148.960282 C0.612750404,142.052729 0,134.946095 0,127.719963 L0,127.719963 L0,127.349037 C0.0121454869,125.473817 0.134939797,123.182933 0.311311815,120.812834 L0.36577283,120.099764 C0.887996182,113.428547 1.78485803,106.400063 1.78485803,106.400063 L1.78485803,106.400063 L76.4997269,106.400063 L23.6658953,53.7047992 C27.8076812,47.8867213 32.4300059,42.4403618 37.4769335,37.4193681 L37.4769335,37.4193681 L37.5023588,37.3926715 C42.5391163,32.3582023 48.0106469,27.7488446 53.8457596,23.6044907 L53.8457596,23.6044907 L106.677048,76.2997796 L106.677048,1.77979593 C113.590195,0.624138494 120.688946,0.0131003068 127.932622,-0.000183105 L127.932622,-0.000183105 L128.080089,-0.000183105 Z M128.067377,95.7600714 L127.945335,95.7600714 C118.436262,95.7600714 109.32891,97.5001809 100.910584,100.661566 C97.7553011,109.043534 96.0085811,118.129275 95.9958684,127.613685 L95.9958684,127.733184 C96.0085811,137.217594 97.7553011,146.303589 100.923296,154.685303 C109.32891,157.846943 118.436262,159.587052 127.945335,159.587052 L128.067377,159.587052 C137.576449,159.587052 146.683802,157.846943 155.089415,154.685303 C158.257411,146.290368 160.004131,137.217594 160.004131,127.733184 L160.004131,127.613685 C160.004131,118.129275 158.257411,109.043534 155.089415,100.661566 C146.683802,97.5001809 137.576449,95.7600714 128.067377,95.7600714 Z" fill="#FF4A00" fill-rule="nonzero">
|
||||
|
||||
</path>
|
||||
</g>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,52 @@
|
||||
import { z, IntegrationDefinition } from '@botpress/sdk'
|
||||
import { TriggerSchema, EventSchema, ZapierTriggersStateName, ZapierTriggersStateSchema } from './src/types'
|
||||
|
||||
export default new IntegrationDefinition({
|
||||
name: 'zapier',
|
||||
version: '0.3.11',
|
||||
title: 'Zapier',
|
||||
description:
|
||||
"Trigger workflows from Zapier or let Zapier trigger your workflows to automate tasks and enhance your bot's capabilities.",
|
||||
icon: 'icon.svg',
|
||||
readme: 'hub.md',
|
||||
configuration: {
|
||||
schema: z.object({}),
|
||||
},
|
||||
channels: {},
|
||||
user: {
|
||||
tags: {
|
||||
id: { title: 'ID', description: 'The user id' },
|
||||
},
|
||||
},
|
||||
states: {
|
||||
[ZapierTriggersStateName]: {
|
||||
type: 'integration',
|
||||
schema: ZapierTriggersStateSchema,
|
||||
},
|
||||
},
|
||||
events: {
|
||||
event: {
|
||||
title: 'Event from Zapier',
|
||||
description: 'This is called when a Zap sends an event to the Zapier webhook of your bot.',
|
||||
schema: EventSchema,
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
trigger: {
|
||||
title: 'Send to Zapier',
|
||||
description: 'This sends a trigger to Zapier which you can use to start a Zap.',
|
||||
input: {
|
||||
schema: TriggerSchema,
|
||||
},
|
||||
output: {
|
||||
schema: z.object({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
attributes: {
|
||||
category: 'Developer Tools',
|
||||
guideSlug: 'zapier',
|
||||
repo: 'botpress',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@botpresshub/zapier",
|
||||
"description": "Zapier integration for Botpress",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "bp add -y && bp build",
|
||||
"check:type": "tsc --noEmit",
|
||||
"check:bplint": "bp lint",
|
||||
"test": "vitest --run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@botpress/client": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"@botpress/sdk-addons": "workspace:*",
|
||||
"axios": "^1.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@botpress/cli": "workspace:*",
|
||||
"@botpress/common": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"jest": "^29.5.0",
|
||||
"ts-jest": "^29.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { z, isApiError } from '@botpress/sdk'
|
||||
import {
|
||||
TriggerSubscriber,
|
||||
ZapierTriggersStateName,
|
||||
ZapierTriggersStateSchema,
|
||||
ZapierTriggersState,
|
||||
Client,
|
||||
} from './types'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export async function unsubscribeZapierHook(url: string, ctx: bp.Context, client: Client) {
|
||||
let subscribers = await getTriggerSubscribers(ctx, client)
|
||||
subscribers = subscribers.filter((x) => x.url !== url)
|
||||
await saveTriggerSubscribers(subscribers, ctx, client)
|
||||
console.info(`Zapier hook ${url} was unsubscribed`)
|
||||
}
|
||||
|
||||
export async function getTriggerSubscribers(ctx: bp.Context, client: Client) {
|
||||
const state = await getTriggersState(ctx, client)
|
||||
return state.subscribers
|
||||
}
|
||||
|
||||
export async function saveTriggerSubscribers(subscribers: TriggerSubscriber[], ctx: bp.Context, client: Client) {
|
||||
await client.setState({
|
||||
type: 'integration',
|
||||
name: ZapierTriggersStateName,
|
||||
id: ctx.integrationId,
|
||||
payload: buildTriggersState({ subscribers }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTriggersState(ctx: bp.Context, client: Client) {
|
||||
const defaultState = buildTriggersState()
|
||||
|
||||
return await client
|
||||
.getState({
|
||||
type: 'integration',
|
||||
name: ZapierTriggersStateName,
|
||||
id: ctx.integrationId,
|
||||
})
|
||||
.then((res) => ZapierTriggersStateSchema.parse(res.state.payload))
|
||||
.catch((e) => {
|
||||
// TODO: Remove hard-coded "No State found" message check once the bridge client correctly receives the ResourceNotFoundError
|
||||
if ((isApiError(e) && e.type === 'ResourceNotFound') || e.message === 'No State found') {
|
||||
console.info("Zapier triggers state doesn't exist yet and will be initialized")
|
||||
return defaultState
|
||||
} else if (z.is.zuiError(e)) {
|
||||
console.warn(`Zapier triggers state will be reset as it's corrupted: ${e.message}`)
|
||||
return defaultState
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function buildTriggersState(partial?: Partial<ZapierTriggersState>) {
|
||||
return <ZapierTriggersState>{
|
||||
subscribers: [],
|
||||
...partial,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { reporting } from '@botpress/sdk-addons'
|
||||
import axios, { isAxiosError } from 'axios'
|
||||
import { constants } from 'http2'
|
||||
import { getTriggerSubscribers, saveTriggerSubscribers, unsubscribeZapierHook } from './helpers'
|
||||
import { Client, TriggerRequestBody, IntegrationEvent, IntegrationEventSchema, EventSchema, Event } from './types'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
const integration = new bp.Integration({
|
||||
register: async () => {},
|
||||
unregister: async () => {},
|
||||
channels: {},
|
||||
actions: {
|
||||
trigger: async ({ ctx, input, client, logger }) => {
|
||||
console.info(`Zapier trigger called with payload: ${JSON.stringify(input)}`)
|
||||
|
||||
const subscribers = await getTriggerSubscribers(ctx, client)
|
||||
|
||||
console.info(`Notifying ${subscribers.length} Zapier trigger REST hooks`)
|
||||
|
||||
for (const { url: zapierHookUrl } of subscribers) {
|
||||
const request: TriggerRequestBody = {
|
||||
botId: ctx.botId,
|
||||
data: input.data,
|
||||
correlationId: input.correlationId,
|
||||
}
|
||||
|
||||
await axios
|
||||
.post(zapierHookUrl, request)
|
||||
.then(() => {
|
||||
console.info(`Successfully notified Zapier trigger REST hook: ${zapierHookUrl}`)
|
||||
})
|
||||
.catch(async (e) => {
|
||||
logger.forBot().warn(`Failed to notify Zapier trigger REST hook: ${zapierHookUrl} (Error: ${e.message})`)
|
||||
console.warn(`Failed to notify Zapier trigger REST hook: ${zapierHookUrl} (Error: ${e.message})`)
|
||||
|
||||
if (isAxiosError(e) && e.response?.status === constants.HTTP_STATUS_GONE) {
|
||||
// Zapier REST hooks will send back a HTTP 410 Gone error if the hook is no longer valid.
|
||||
await unsubscribeZapierHook(zapierHookUrl, ctx, client)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {}
|
||||
},
|
||||
},
|
||||
handler: async ({ req, ctx, client }) => {
|
||||
if (!req.body) {
|
||||
console.warn('Event handler received an empty body')
|
||||
return
|
||||
}
|
||||
|
||||
const body = JSON.parse(req.body)
|
||||
|
||||
const integrationEventParse = IntegrationEventSchema.safeParse(body)
|
||||
if (integrationEventParse.success) {
|
||||
await handleIntegrationEvent(integrationEventParse.data, ctx, client)
|
||||
return
|
||||
}
|
||||
|
||||
const eventParse = EventSchema.safeParse(body)
|
||||
if (!eventParse.success) {
|
||||
console.warn(`Received invalid event: ${eventParse.error}`)
|
||||
return
|
||||
}
|
||||
|
||||
await handleEvent(eventParse.data, client)
|
||||
},
|
||||
})
|
||||
|
||||
export default reporting.wrapIntegration(integration)
|
||||
|
||||
async function handleIntegrationEvent(event: IntegrationEvent, ctx: bp.Context, client: Client) {
|
||||
console.info('Received integration event: ', event)
|
||||
|
||||
let subscribers = await getTriggerSubscribers(ctx, client)
|
||||
|
||||
if (event.action === 'subscribe:triggers') {
|
||||
subscribers.push({ url: event.url })
|
||||
|
||||
// Send a demo trigger call to the Zapier REST hook so the user can easily test it when setting up the bot trigger in their Zap.
|
||||
await axios
|
||||
.post(event.url, <TriggerRequestBody>{
|
||||
botId: ctx.botId,
|
||||
data: '{"message": "Hello from Botpress! This is an automated test message to confirm that your bot is now able to trigger this Zap."}',
|
||||
correlationId: '12345',
|
||||
})
|
||||
.then(() => {
|
||||
console.info(`Successfully sent demo trigger to Zapier REST hook: ${event.url}`)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn(`Failed to send demo trigger to Zapier REST hook: ${event.url} (Error: ${e.message})`)
|
||||
})
|
||||
} else if (event.action === 'unsubscribe:triggers') {
|
||||
subscribers = subscribers.filter((x) => x.url !== event.url)
|
||||
}
|
||||
|
||||
await saveTriggerSubscribers(subscribers, ctx, client)
|
||||
|
||||
console.info(`Successfully updated trigger subscribers: ${JSON.stringify(subscribers)}`)
|
||||
}
|
||||
|
||||
export async function handleEvent(event: Event, client: Client) {
|
||||
await client.createEvent({
|
||||
type: 'event',
|
||||
payload: event,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export type Client = bp.Client
|
||||
|
||||
export const TriggerSubscriberSchema = z.object({
|
||||
url: z.string().title('URL').describe('The webhook URL of the subscriber'),
|
||||
})
|
||||
|
||||
export type TriggerSubscriber = z.infer<typeof TriggerSubscriberSchema>
|
||||
|
||||
export const ZapierTriggersStateName = 'triggers' as const
|
||||
|
||||
export const ZapierTriggersStateSchema = z.object({
|
||||
subscribers: z.array(TriggerSubscriberSchema).describe('The subscribers').title('Subscribers'),
|
||||
})
|
||||
|
||||
export type ZapierTriggersState = z.infer<typeof ZapierTriggersStateSchema>
|
||||
|
||||
export const IntegrationEventSchema = z.object({
|
||||
action: z.enum(['subscribe:triggers', 'unsubscribe:triggers']),
|
||||
url: z.string(),
|
||||
})
|
||||
|
||||
export type IntegrationEvent = z.infer<typeof IntegrationEventSchema>
|
||||
|
||||
export const TriggerSchema = z.object({
|
||||
data: z
|
||||
.string()
|
||||
.placeholder('{ "message": "Hello Zapier!" }')
|
||||
.describe('The data you want to send to Zapier trigger. Can be any string including JSON.')
|
||||
.title('Trigger Data to send to Zapier'),
|
||||
correlationId: z
|
||||
.string()
|
||||
.title('Correlation ID')
|
||||
.describe('Can be used to receive a response back from Zapier by listening for an `event.correlationId`')
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type Trigger = z.infer<typeof TriggerSchema>
|
||||
|
||||
export type TriggerRequestBody = Trigger & {
|
||||
botId: string
|
||||
}
|
||||
|
||||
export const EventSchema = z.object({
|
||||
data: z
|
||||
.string()
|
||||
.placeholder('{ "message": "Hello Botpress!" }')
|
||||
.describe('The data the Zapier action sent. Can be any string including JSON.')
|
||||
.title('Event Data received from Zapier'),
|
||||
correlationId: z
|
||||
.string()
|
||||
.title('Correlation ID')
|
||||
.describe('Can be used to correlate the response from Zapier when used with an `action.correlationId`')
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type Event = z.infer<typeof EventSchema>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": { "*": ["./*"] },
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import config from '../../vitest.config'
|
||||
export default config
|
||||
Reference in New Issue
Block a user