chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:48 +08:00
commit 77bb5bf71f
3762 changed files with 353249 additions and 0 deletions
+61
View File
@@ -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,
}
}
+107
View File
@@ -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,
})
}
+59
View File
@@ -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>