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
+13
View File
@@ -0,0 +1,13 @@
import rootConfig from '../../eslint.config.mjs'
export default [
...rootConfig,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
},
]
+34
View File
@@ -0,0 +1,34 @@
## Description
Connect your Botpress chatbot with Mailchimp, a popular email marketing platform. With this integration, you can easily manage your email campaigns and subscribers directly from your chatbot.
To set up the integration, youll need to provide your Mailchimp API key and Server Prefix (e.g. us19). Once the integration is set up, you can use the built-in actions to add customer to campaign or list, send mass campaigns, and more.
For more detailed instructions on how to set up and use the Botpress Mailchimp integration, please refer to our documentation.
## Prerequisites
Before enabling the Botpress Mailchimp Integration, please ensure that you have the following:
- A Botpress cloud account.
- Access to a Mailchimp account.
- API key generated from your Mailchimp account.
## Enable Integration
To enable the Mailchimp integration in Botpress, follow these steps:
- Access your Botpress admin panel.
- Navigate to the “Integrations” section.
- Locate the Mailchimp integration and click on “Enable” or “Configure.”
- Login to your Mailchimp account.
- Generate an API key for your Mailchimp account by following the instructions [here](https://mailchimp.com/help/about-api-keys/#Generate_an_API_key).
- Note the url prefix (e.g. us19) for your Mailchimp account by looking at the url of the Mailchimp admin panel.
- Paste the API Key and Server Prefix into the Botpress Mailchimp integration configuration.
- Save the configuration.
## Usage
Once the integration is enabled, you can start sending and receiving emails within your Botpress chatbot. The integration offers actions such as `addCustomerToCampaign`, `addCustomerToList`, and `sendMassEmailCampaign` that can be used to interact with your Mailchimp account.
For more detailed information and examples, refer to the Botpress documentation or the Mailchimp documentation for configuring the integration.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1,86 @@
import { z, IntegrationDefinition } from '@botpress/sdk'
import {
addCustomerToCampaignInputSchema,
addCustomerToListInputSchema,
sendMassEmailCampaignInputSchema,
addCustomerOutputSchema,
sendMassEmailCampaignOutputSchema,
getAllListsOutputSchema,
getAllListsInputSchema,
getAllCampaignsOutputSchema,
getAllCampaignsInputSchema,
} from './src/misc/custom-schemas'
const INTEGRATION_NAME = 'mailchimp'
export default new IntegrationDefinition({
name: INTEGRATION_NAME,
title: 'Mailchimp',
version: '0.3.12',
readme: 'hub.md',
icon: 'icon.svg',
description: 'Send mass email campaigns from within your workflows. Manage customers, campaigns, lists and more.',
channels: {},
configuration: {
schema: z.object({
apiKey: z.string().min(1).describe('Your API Key').title('Api Key'),
serverPrefix: z.string().min(1).describe('Your Server Prefix').title('Server Prefix'),
}),
},
actions: {
addCustomerToCampaign: {
title: 'Add Customer Profile to Campaign',
description: "Adds a Customer's Profile to a Campaign",
input: {
schema: addCustomerToCampaignInputSchema,
},
output: {
schema: addCustomerOutputSchema,
},
},
addCustomerToList: {
title: 'Add Customer Profile to List/Audience',
description: "Add Customer's Profile to a List/Audience",
input: {
schema: addCustomerToListInputSchema,
},
output: {
schema: addCustomerOutputSchema,
},
},
sendMassEmailCampaign: {
title: 'Mass Mailing of the Campaign',
description: 'Mass Mailing of the Campaign by its IDs',
input: {
schema: sendMassEmailCampaignInputSchema,
},
output: {
schema: sendMassEmailCampaignOutputSchema,
},
},
getAllLists: {
title: 'Get All Email Lists/Audiences',
description: 'Get all available email lists/audiences',
input: {
schema: getAllListsInputSchema,
},
output: {
schema: getAllListsOutputSchema,
},
},
getAllCampaigns: {
title: 'Get All Campaigns',
description: 'Get all available campaigns',
input: {
schema: getAllCampaignsInputSchema,
},
output: {
schema: getAllCampaignsOutputSchema,
},
},
},
attributes: {
category: 'Marketing & Email',
repo: 'botpress',
},
})
+21
View File
@@ -0,0 +1,21 @@
{
"name": "@botpresshub/mailchimp",
"scripts": {
"build": "bp add -y && bp build",
"check:type": "tsc --noEmit",
"check:bplint": "bp lint",
"test": "vitest --run"
},
"private": true,
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/sdk": "workspace:*",
"@mailchimp/mailchimp_marketing": "^3.0.80"
},
"devDependencies": {
"@botpress/cli": "workspace:*",
"@botpress/common": "workspace:*",
"@botpress/sdk": "workspace:*",
"@types/mailchimp__mailchimp_marketing": "3.0.10"
}
}
@@ -0,0 +1,28 @@
import { getMailchimpClient, getValidCustomer, parseError } from 'src/utils'
import { addCustomerOutputSchema, addCustomerToCampaignInputSchema } from '../misc/custom-schemas'
import * as bp from '.botpress'
export const addCustomerToCampaign: bp.IntegrationProps['actions']['addCustomerToCampaign'] = async ({
ctx,
logger,
input,
}) => {
try {
const mailchimpClient = getMailchimpClient(ctx.configuration, logger)
const validatedInput = addCustomerToCampaignInputSchema.parse(input)
const customer = getValidCustomer(validatedInput)
const response = await mailchimpClient.addCustomerToCampaignList(validatedInput.campaignId, customer)
return addCustomerOutputSchema.parse({
email_address: response.email_address,
status: response.status,
list_id: response.list_id,
id: response.id,
})
} catch (err: any) {
const error = parseError(err)
logger.forBot().error('Error adding customer to campaign', error)
throw error
}
}
@@ -0,0 +1,28 @@
import { getMailchimpClient, getValidCustomer, parseError } from 'src/utils'
import { addCustomerOutputSchema, addCustomerToListInputSchema } from '../misc/custom-schemas'
import * as bp from '.botpress'
export const addCustomerToList: bp.IntegrationProps['actions']['addCustomerToList'] = async ({
ctx,
input,
logger,
}) => {
try {
const validatedInput = addCustomerToListInputSchema.parse(input)
const mailchimpClient = getMailchimpClient(ctx.configuration, logger)
const customer = getValidCustomer(validatedInput)
const response = await mailchimpClient.addCustomerToList(validatedInput.listId, customer)
return addCustomerOutputSchema.parse({
email_address: response.email_address,
status: response.status,
list_id: response.list_id,
id: response.id,
})
} catch (err: any) {
const error = parseError(err)
logger.forBot().error('Error adding customer to campaign', error)
throw error
}
}
@@ -0,0 +1,16 @@
import { getMailchimpClient, parseError } from 'src/utils'
import * as bp from '.botpress'
export const getAllCampaigns: bp.IntegrationProps['actions']['getAllCampaigns'] = async ({ ctx, logger, input }) => {
try {
const mailchimpClient = getMailchimpClient(ctx.configuration, logger)
const allCampaigns = await mailchimpClient.getAllCampaigns({
count: input.count || 100,
})
return allCampaigns as bp.actions.getAllCampaigns.output.Output
} catch (err) {
const error = parseError(err)
logger.forBot().error('Error adding customer to campaign', error)
throw error
}
}
@@ -0,0 +1,15 @@
import { getMailchimpClient, parseError } from 'src/utils'
import * as bp from '.botpress'
export const getAllLists: bp.IntegrationProps['actions']['getAllLists'] = async ({ ctx, logger, input }) => {
try {
const mailchimpClient = getMailchimpClient(ctx.configuration)
return await mailchimpClient.getAllLists({
count: input.count || 100,
})
} catch (err) {
const error = parseError(err)
logger.forBot().error('Error adding customer to campaign', error)
throw error
}
}
@@ -0,0 +1,13 @@
import { addCustomerToCampaign } from './add-customer-to-campaign'
import { addCustomerToList } from './add-customer-to-list'
import { getAllCampaigns } from './get-all-campaigns'
import { getAllLists } from './get-all-lists'
import { sendMassEmailCampaign } from './send-mass-email-campaign'
export default {
addCustomerToCampaign,
addCustomerToList,
sendMassEmailCampaign,
getAllLists,
getAllCampaigns,
}
@@ -0,0 +1,31 @@
import { RuntimeError } from '@botpress/sdk'
import { sendMassEmailCampaignInputSchema, sendMassEmailCampaignOutputSchema } from '../misc/custom-schemas'
import { getMailchimpClient, parseError } from '../utils'
import * as bp from '.botpress'
export const sendMassEmailCampaign: bp.IntegrationProps['actions']['sendMassEmailCampaign'] = async ({
ctx,
input,
logger,
}) => {
try {
const validatedInput = sendMassEmailCampaignInputSchema.parse(input)
const mailchimpClient = getMailchimpClient(ctx.configuration, logger)
const response = await mailchimpClient.sendMassEmailCampaign(validatedInput.campaignIds)
if (typeof response === 'undefined') {
throw new RuntimeError('Batch client is not available')
}
return sendMassEmailCampaignOutputSchema.parse({
id: response.id,
status: response.status,
total_operations: response.total_operations,
_links: response._links,
})
} catch (err) {
const error = parseError(err)
logger.forBot().error('Error adding customer to campaign', error)
throw error
}
}
+159
View File
@@ -0,0 +1,159 @@
import { RuntimeError } from '@botpress/sdk'
import mailchimp, { lists } from '@mailchimp/mailchimp_marketing'
import type {
MailchimpClient,
Customer,
Operation,
addCustomerFullOutputType,
getAllListsOutputType,
getAllListsInputType,
getAllCampaignsOutputType,
getAllCampaignsInputType,
} from 'src/misc/custom-types'
import { Logger as IntegrationLogger } from 'src/misc/types'
import { isMailchimpError } from 'src/utils'
import {
addCustomerFullOutputSchema,
getAllCampaignsOutputSchema,
getAllListsOutputSchema,
} from '../misc/custom-schemas'
export class MailchimpApi {
private _client: MailchimpClient
private _logger?: IntegrationLogger
public constructor(apiKey: string, serverPrefix: string, logger?: IntegrationLogger) {
this._client = mailchimp
this._client.setConfig({
apiKey,
server: serverPrefix,
})
this._logger = logger
}
public getAllCampaigns = async (input: getAllCampaignsInputType): Promise<getAllCampaignsOutputType> => {
const response = await this._client.campaigns?.list(input)
return getAllCampaignsOutputSchema.parse(response)
}
public getAllLists = async (input: getAllListsInputType): Promise<getAllListsOutputType> => {
const response = await this._client.lists.getAllLists(input)
return getAllListsOutputSchema.parse(response)
}
public addCustomerToList = async (listId: string, customer: Customer): Promise<addCustomerFullOutputType> => {
this._logger?.forBot().debug('Adding customer to list', { listId, customer })
const existing = await this.getListCustomer(listId, customer.email)
if (existing) {
this._logger?.forBot().debug('Customer already exists in list', { existing })
return addCustomerFullOutputSchema.parse(existing)
}
const listResponse = await this._client.lists.addListMember(
listId,
{
email_address: customer.email,
status: 'subscribed',
language: customer.language || '',
merge_fields: {
FNAME: customer.firstName || '',
LNAME: customer.lastName || '',
BIRTHDAY: customer.birthday || '',
ADDRESS: {
addr1: customer.address1 || '',
addr2: customer.address2 || '',
city: customer.city || '',
state: customer.state || '',
zip: customer.zip || '',
country: customer.country || '',
},
PHONE: customer.phone || '',
},
},
{ skipMergeValidation: true }
)
return addCustomerFullOutputSchema.parse(listResponse)
}
public addCustomerToCampaignList = async (
campaignId: string,
customer: Customer
): Promise<addCustomerFullOutputType> => {
this._logger?.forBot().debug('Adding customer to campaign list', { campaignId, customer })
const listId = await this._getCampaignListID(campaignId)
this._logger?.forBot().debug('Found Campaign list ID', { listId })
if (!listId) {
throw new RuntimeError(`Campaign ${campaignId} does not have an associated list`)
}
const existing = await this.getListCustomer(listId, customer.email)
if (existing) {
this._logger?.forBot().debug('Customer already exists in list', { existing })
return addCustomerFullOutputSchema.parse(existing)
}
this._logger?.forBot().debug('Customer does not exist in list, adding', { customer })
return this.addCustomerToList(listId, customer)
}
public getListCustomer = async (
listId: string,
customerEmail: string
): Promise<lists.MembersSuccessResponse | null> => {
try {
const response = await this._client.lists.getListMember(listId, customerEmail)
this._logger?.forBot().debug('Found customer in list', { response })
if (isMailchimpError(response)) {
throw response
}
return response as lists.MembersSuccessResponse
} catch (error) {
if ((error as mailchimp.ErrorResponse).status === 404) {
this._logger?.forBot().debug('Customer does not exist in list', { customerEmail })
return null
} else {
throw error
}
}
}
public getCampaignListCustomer = async (campaignId: string, customerEmail: string) => {
const listId = await this._getCampaignListID(campaignId)
if (!listId) {
throw new RuntimeError(`Campaign ${campaignId} does not have an associated list, is the ID correct? `)
}
return this.getListCustomer(listId, customerEmail)
}
public sendMassEmailCampaign = async (campaignIds: string | string[]) => {
let campaignIdsArray = typeof campaignIds === 'string' ? campaignIds.split(',') : campaignIds
campaignIdsArray = campaignIdsArray.map((campaignId) => campaignId.trim())
const operations: Operation[] = campaignIdsArray.map((campaignId) => ({
method: 'POST',
path: `/campaigns/${campaignId}/actions/send`,
}))
const response = await this._client.batches?.start({
operations,
})
return response
}
private _getCampaignListID = async (campaignId: string): Promise<string | null> => {
try {
const campaignResponse = await this._client.campaigns?.get(campaignId)
return campaignResponse?.recipients.list_id || null
} catch (error: any) {
if (isMailchimpError(error) && error.status === 404) {
return null
}
throw error
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import actions from './actions'
import * as bp from '.botpress'
export default new bp.Integration({
register: async () => {},
unregister: async () => {},
actions,
channels: {},
handler: async () => {
throw new Error('Not implemented')
},
})
@@ -0,0 +1,179 @@
import { z } from '@botpress/sdk'
import {
linkSchema,
tagsSchema,
memberLastNoteSchema,
memberMarketingPermissionsSchema,
fullMemberLocationSchema,
memberStatsSchema,
batchStatusSchema,
listSchema,
constraintsSchema,
recipientsSchema,
settingsSchema,
variateSettingsSchema,
trackingSchema,
rssOptsSchema,
abSplitOptsSchema,
socialCardSchema,
reportSummarySchema,
deliveryStatusSchema,
} from './sub-schemas'
export const customerSchema = z.object({
email: z.string().email().describe('The email address of the customer (e.g. example@example.com)').title('Email'),
firstName: z.string().optional().describe('The first name of the customer (e.g. John)').title('First Name'),
lastName: z.string().optional().describe('The last name of the customer (e.g. Doe)').title('Last Name'),
company: z.string().optional().describe('The company of the customer (e.g. Acme Inc.)').title('Company'),
birthday: z.string().optional().describe('The birthday of the customer (e.g. 01/01/2000)').title('Birthday'),
language: z.string().optional().describe('The language of the customer (e.g. en)').title('Language'),
address1: z
.string()
.optional()
.describe('The first line of the address of the customer (e.g. 123 St Marie.)')
.title('Address 1'),
address2: z
.string()
.optional()
.describe('The second line of the address of the customer (e.g. Apt. 4B)')
.title('Address 2'),
city: z.string().optional().describe('The city of the customer (e.g. Anytown)').title('City'),
state: z.string().optional().describe('The state or province of the customer (e.g. CA)').title('State'),
zip: z.string().optional().describe('The zip or postal code of the customer (e.g. 12345)').title('Zip'),
country: z.string().optional().describe('The country of the customer (e.g. USA)').title('Country'),
phone: z.string().optional().describe('The phone number of the customer (e.g. 555-1234)').title('Phone'),
})
export const addCustomerToCampaignInputSchema = customerSchema.extend({
campaignId: z
.string()
.describe('The ID of the Mailchimp campaign (e.g. f6g7h8i9j0)')
.title('The ID of the Mailchimp campaign (e.g. f6g7h8i9j0)'),
})
export const addCustomerToListInputSchema = customerSchema.extend({
listId: z
.string()
.describe('The ID of the Mailchimp list or audience (e.g. a1b2c3d4e5)')
.title('The ID of the Mailchimp list or audience (e.g. a1b2c3d4e5)'),
})
export const sendMassEmailCampaignInputSchema = z.object({
campaignIds: z
.string()
.describe('The Campaign IDs (Can be either a string with comma-separated IDs)')
.title('The Campaign IDs (Can be either a string with comma-separated IDs)'),
})
export const addCustomerOutputSchema = z.object({
id: z.string().describe('The id of the customer').title('ID'),
email_address: z.string().describe('The email address of the customer').title('Email Address'),
status: z.string().describe('The status of the customer').title('Status'),
list_id: z.string().describe('The list id of the customer').title('List ID'),
})
export const addCustomerFullOutputSchema = z.object({
id: z.string().optional(),
email_address: z.string().optional(),
unique_email_id: z.string().optional(),
contact_id: z.string().optional(),
full_name: z.string().optional(),
web_id: z.number().optional(),
email_type: z.string().optional(),
status: z.string().optional(),
unsubscribe_reason: z.string().optional(),
consents_to_one_to_one_messaging: z.boolean().optional(),
merge_fields: z.record(z.any()).optional(),
interests: z.record(z.any()).optional(),
stats: memberStatsSchema.optional(),
ip_signup: z.string().optional(),
timestamp_signup: z.string().optional(),
ip_opt: z.string().optional(),
timestamp_opt: z.string().optional(),
member_rating: z.union([z.number(), z.string()]).optional(),
last_changed: z.string().optional(),
language: z.string().optional(),
vip: z.boolean().optional(),
email_client: z.string().optional(),
location: fullMemberLocationSchema.optional(),
marketing_permissions: z.array(memberMarketingPermissionsSchema).optional(),
last_note: memberLastNoteSchema.optional(),
source: z.string().optional(),
tags_count: z.number().optional(),
tags: z.array(tagsSchema).optional(),
list_id: z.string().optional(),
_links: z.array(linkSchema).optional(),
message: z.string().optional(),
})
export const sendMassEmailCampaignOutputSchema = z.object({
id: z.string().optional().describe('The id of the campaign').title('ID'),
status: batchStatusSchema.optional().describe('The status of the campaign').title('Status'),
total_operations: z
.number()
.optional()
.describe('The number of operation done in the camplain')
.title('Total Operation'),
_links: z.array(linkSchema).optional().describe('Link').title('Link'),
})
export const sendMassEmailCampaignFullOutputSchema = z.object({
id: z.string(),
status: batchStatusSchema,
total_operations: z.number(),
finished_operations: z.number(),
errored_operations: z.number(),
submitted_at: z.string(),
completed_at: z.string(),
response_body_url: z.string(),
_links: z.array(linkSchema),
})
export const getAllListsOutputSchema = z.object({
lists: z.array(listSchema).describe('The array of list').title('Lists'),
constraints: constraintsSchema.describe('The constraints of the lists').title('Constraints'),
_links: z.array(linkSchema).describe('Link').title('Link'),
})
export const getAllListsInputSchema = z.object({
count: z.number().optional().default(100).describe('List count to retrieve').title('Count'),
})
export const getAllCampaignsInputSchema = getAllListsInputSchema.describe('List count to retrieve').title('Lists')
const campaignSchema = z.object({
id: z.string().title('ID').describe('Campaign ID'),
web_id: z.number().title('Web ID').describe('Campaign web ID'),
parent_campaign_id: z
.string()
.optional()
.title('Parent Campaign ID')
.describe('Parent campaign ID if this is a child campaign'),
type: z.string().title('Type').describe('Campaign type'),
create_time: z.string().title('Create Time').describe('Campaign creation time'),
archive_url: z.string().title('Archive URL').describe('URL to the campaign archive'),
long_archive_url: z.string().title('Long Archive URL').describe('Long URL to the campaign archive'),
status: z.string().title('Status').describe('Campaign status'),
emails_sent: z.number().title('Emails Sent').describe('Number of emails sent'),
send_time: z.string().title('Send Time').describe('Time when the campaign was sent'),
content_type: z.string().title('Content Type').describe('Campaign content type'),
needs_block_refresh: z.boolean().title('Needs Block Refresh').describe('Whether the campaign needs block refresh'),
resendable: z.boolean().title('Resendable').describe('Whether the campaign can be resent'),
recipients: recipientsSchema.optional().title('Recipients').describe('Campaign recipients'),
settings: settingsSchema.title('Settings').describe('Campaign settings'),
variate_settings: variateSettingsSchema.optional().title('Variate Settings').describe('A/B testing settings'),
tracking: trackingSchema.title('Tracking').describe('Campaign tracking settings'),
rss_opts: rssOptsSchema.optional().title('RSS Options').describe('RSS campaign options'),
ab_split_opts: abSplitOptsSchema.optional().title('AB Split Options').describe('A/B split test options'),
social_card: socialCardSchema.optional().title('Social Card').describe('Social media card settings'),
report_summary: reportSummarySchema.optional().title('Report Summary').describe('Campaign report summary'),
delivery_status: deliveryStatusSchema.title('Delivery Status').describe('Campaign delivery status'),
_links: z.array(linkSchema).title('Links').describe('Related links'),
})
export const getAllCampaignsOutputSchema = z.object({
campaigns: z.array(campaignSchema).describe('The list of campaings').title('Campaigns'),
total_items: z.number().describe('Total number of items').title('Total Items'),
_links: z.array(linkSchema).describe('Links').title('Links'),
})
@@ -0,0 +1,97 @@
import { z } from '@botpress/sdk'
import { HttpMethod, Link, Config, lists, ErrorResponse } from '@mailchimp/mailchimp_marketing'
import {
addCustomerFullOutputSchema,
getAllCampaignsInputSchema,
getAllCampaignsOutputSchema,
getAllListsInputSchema,
getAllListsOutputSchema,
} from './custom-schemas'
export type Operation = {
method: HttpMethod
path: string
params?: object
body?: string
operation_id?: string
}
export type BatchStatus = 'pending' | 'preprocessing' | 'started' | 'finalizing' | 'finished'
export type BatchResponse = {
id: string
status: BatchStatus
total_operations: number
finished_operations: number
errored_operations: number
submitted_at: string
completed_at: string
response_body_url: string
_links: Link[]
}
export type MailchimpClient = {
setConfig: (config: Config) => void
lists: {
getAllLists: typeof lists.getAllLists
addListMember: typeof lists.addListMember
getListMember: typeof lists.getListMember
}
campaigns?: {
get: (campaignId: string) => Promise<{ recipients: { list_id: string } }>
list: (input: { count?: number }) => Promise<any>
}
batches?: {
start: (batch: { operations: Operation[] }) => Promise<BatchResponse>
}
}
export type addCustomerFullOutputType = z.infer<typeof addCustomerFullOutputSchema>
export type getAllListsInputType = z.infer<typeof getAllListsInputSchema>
export type getAllListsOutputType = z.infer<typeof getAllListsOutputSchema>
export type getAllCampaignsInputType = z.infer<typeof getAllCampaignsInputSchema>
export type getAllCampaignsOutputType = z.infer<typeof getAllCampaignsOutputSchema>
export type Customer = {
email: string
firstName?: string
lastName?: string
company?: string
birthday?: string
language?: string
address1?: string
address2?: string
city?: string
state?: string
zip?: string
country?: string
phone?: string
}
export type MailchimpAPIError = {
status: number
response: HTTPResponse<ErrorResponse>
}
type HTTPResponse<T> = {
request: any
req: any
text: string
body: T
headers: Record<string, string>
status: number
clientError: boolean
serverError: boolean
accepted: boolean
noContent: boolean
badRequest: boolean
unauthorized: boolean
notAcceptable: boolean
forbidden: boolean
notFound: boolean
type: string
charset: string
links: Record<string, string>
}
@@ -0,0 +1,353 @@
import { z } from '@botpress/sdk'
const HttpMethodSchema = z.union([
z.literal('GET'),
z.literal('POST'),
z.literal('PUT'),
z.literal('PATCH'),
z.literal('DELETE'),
z.literal('OPTIONS'),
z.literal('HEAD'),
])
const linkSchema = z.object({
rel: z.string().optional().title('Rel').describe('Link relation type'),
href: z.string().optional().title('Href').describe('Link URL'),
method: HttpMethodSchema.optional().title('Method').describe('HTTP method'),
targetSchema: z.string().optional().title('Target Schema').describe('Target schema URL'),
schema: z.string().optional().title('Schema').describe('Schema URL'),
})
const tagsSchema = z.object({
id: z.number().optional(),
name: z.string().optional(),
})
const memberLastNoteSchema = z.object({
note_id: z.number().optional(),
created_at: z.string().optional(),
created_by: z.string().optional(),
note: z.string().optional(),
})
const memberMarketingPermissionsInputSchema = z.object({
marketing_permission_id: z.string().optional(),
enabled: z.boolean().optional(),
})
const memberMarketingPermissionsSchema = memberMarketingPermissionsInputSchema.extend({
text: z.string().optional(),
})
const memberLocationSchema = z.object({
latitude: z.number(),
longitude: z.number(),
})
const fullMemberLocationSchema = memberLocationSchema.extend({
gmtoff: z.number().optional(),
dstoff: z.number().optional(),
country_code: z.string().optional(),
timezone: z.string().optional(),
region: z.string().optional(),
})
const memberEcommerceDataSchema = z.object({
total_revenue: z.number().optional(),
number_of_orders: z.number().optional(),
currency_code: z.number().optional(),
})
const memberStatsSchema = z.object({
avg_open_rate: z.number().optional(),
avg_click_rate: z.number().optional(),
ecommerce_data: memberEcommerceDataSchema.optional(),
})
const batchStatusSchema = z.union([
z.literal('pending'),
z.literal('preprocessing'),
z.literal('started'),
z.literal('finalizing'),
z.literal('finished'),
])
const contactSchema = z.object({
company: z.string().describe('Company name'),
address1: z.string().describe('Address line 1'),
address2: z.string().describe('Address line 2'),
city: z.string().describe('City'),
state: z.string().describe('State'),
zip: z.string().describe('ZIP code'),
country: z.string().describe('Country'),
phone: z.string().describe('Phone number'),
})
const campaignDefaultsSchema = z.object({
from_name: z.string().describe('Default sender name'),
from_email: z.string().email().describe('Default sender email'),
subject: z.string().describe('Default email subject'),
language: z.string().describe('Default email language'),
})
const statsSchema = z.object({
member_count: z.number().describe('Member count'),
unsubscribe_count: z.number().describe('Unsubscribe count'),
cleaned_count: z.number().describe('Cleaned count'),
member_count_since_send: z.number().describe('Member count since last send'),
unsubscribe_count_since_send: z.number().describe('Unsubscribe count since last send'),
cleaned_count_since_send: z.number().describe('Cleaned count since last send'),
campaign_count: z.number().describe('Campaign count'),
campaign_last_sent: z.string().describe('Last campaign sent date'),
merge_field_count: z.number().describe('Merge field count'),
avg_sub_rate: z.number().describe('Average subscription rate'),
avg_unsub_rate: z.number().describe('Average unsubscribe rate'),
target_sub_rate: z.number().describe('Target subscription rate'),
open_rate: z.number().describe('Open rate'),
click_rate: z.number().describe('Click rate'),
last_sub_date: z.string().describe('Last subscription date'),
last_unsub_date: z.string().describe('Last unsubscribe date'),
})
const listSchema = z.object({
id: z.string().title('ID').describe('List ID'),
web_id: z.number().title('Web ID').describe('List web ID'),
name: z.string().title('Name').describe('List name'),
contact: contactSchema.title('Contact').describe('Contact information for the list'),
permission_reminder: z.string().title('Permission Reminder').describe('Permission reminder message'),
use_archive_bar: z.boolean().title('Use Archive Bar').describe('Whether to use the archive bar'),
campaign_defaults: campaignDefaultsSchema.title('Campaign Defaults').describe('Default campaign settings'),
notify_on_subscribe: z.string().title('Notify on Subscribe').describe('Email to notify on subscription'),
notify_on_unsubscribe: z.string().title('Notify on Unsubscribe').describe('Email to notify on unsubscription'),
date_created: z.string().title('Date Created').describe('Date when the list was created'),
list_rating: z.number().title('List Rating').describe('List quality rating'),
email_type_option: z.boolean().title('Email Type Option').describe('Whether email type option is enabled'),
subscribe_url_short: z.string().title('Subscribe URL Short').describe('Short subscription URL'),
subscribe_url_long: z.string().title('Subscribe URL Long').describe('Long subscription URL'),
beamer_address: z.string().title('Beamer Address').describe('Beamer email address'),
visibility: z.string().title('Visibility').describe('List visibility setting'),
double_optin: z.boolean().title('Double Opt-in').describe('Whether double opt-in is required'),
has_welcome: z.boolean().title('Has Welcome').describe('Whether the list has a welcome email'),
marketing_permissions: z
.boolean()
.title('Marketing Permissions')
.describe('Whether marketing permissions are enabled'),
modules: z.array(z.any()).title('Modules').describe('List modules'),
stats: statsSchema.title('Stats').describe('List statistics'),
_links: z.array(linkSchema).title('Links').describe('Related links'),
})
const constraintsSchema = z.object({
may_create: z.boolean().describe('May create flag'),
max_instances: z.number().describe('Maximum instances allowed'),
current_total_instances: z.number().describe('Current total instances'),
})
const recipientsSchema = z
.object({
list_id: z.string().describe('ID of the list'),
list_is_active: z.boolean().describe('Indicates whether the list is active'),
list_name: z.string().describe('Name of the list'),
segment_text: z.string().describe('Text description of the segment'),
recipient_count: z.number().describe('Count of recipients'),
segment_opts: z
.object({
saved_segment_id: z.number().describe('ID of the saved segment'),
prebuilt_segment_id: z.string().describe('ID of the prebuilt segment'),
match: z.string().describe('Match condition'),
conditions: z.array(z.unknown()).describe('Array of conditions'), // Adjust the type accordingly
})
.partial()
.title('Segment Options')
.describe('Segment options for targeting specific recipients'),
})
.passthrough()
.describe('Recipients information')
const settingsSchema = z
.object({
subject_line: z.string().describe('Subject line of the campaign'),
preview_text: z.string().describe('Preview text of the campaign'),
title: z.string().describe('Title of the campaign'),
from_name: z.string().describe("Sender's name"),
reply_to: z.string().describe('Reply-to email address'),
use_conversation: z.boolean().describe('Indicates whether to use conversation view'),
to_name: z.string().describe("Recipient's name"),
folder_id: z.string().describe('ID of the folder'),
authenticate: z.boolean().describe('Indicates whether authentication is enabled'),
auto_footer: z.boolean().describe('Indicates whether auto-footer is enabled'),
inline_css: z.boolean().describe('Indicates whether inline CSS is enabled'),
auto_tweet: z.boolean().describe('Indicates whether auto-tweet is enabled'),
auto_fb_post: z.array(z.string()).optional().describe('Array of Facebook post strings'),
fb_comments: z.boolean().describe('Indicates whether Facebook comments are enabled'),
timewarp: z.boolean().describe('Indicates whether timewarp is enabled'),
template_id: z.number().describe('ID of the template'),
drag_and_drop: z.boolean().describe('Indicates whether drag-and-drop is enabled'),
})
.passthrough()
.describe('Settings for the campaign')
const variateSettingsSchema = z
.object({
winning_combination_id: z.string().describe('ID of the winning combination'),
winning_campaign_id: z.string().describe('ID of the winning campaign'),
winner_criteria: z.string().describe('Criteria for choosing the winner'),
wait_time: z.number().describe('Wait time'),
test_size: z.number().describe('Size of the test'),
subject_lines: z.array(z.string()).describe('Array of subject lines'),
send_times: z.array(z.string()).describe('Array of send times'),
from_names: z.array(z.string()).describe('Array of sender names'),
reply_to_addresses: z.array(z.string()).describe('Array of reply-to email addresses'),
contents: z.array(z.string()).describe('Array of content strings'),
combinations: z
.array(
z.object({
id: z.string().describe('ID of the combination'),
subject_line: z.number().describe('Subject line'),
send_time: z.number().describe('Send time'),
from_name: z.number().describe("Sender's name"),
reply_to: z.number().describe('Reply-to'),
content_description: z.number().describe('Content description'),
recipients: z.number().describe('Recipients'),
})
)
.describe('Array of combinations'),
})
.passthrough()
const trackingSchema = z
.object({
opens: z.boolean().describe('Indicates whether opens tracking is enabled'),
html_clicks: z.boolean().describe('Indicates whether HTML clicks tracking is enabled'),
text_clicks: z.boolean().describe('Indicates whether text clicks tracking is enabled'),
goal_tracking: z.boolean().describe('Indicates whether goal tracking is enabled'),
ecomm360: z.boolean().describe('Indicates whether Ecomm360 is enabled'),
google_analytics: z.string().describe('Google Analytics tracking code'),
clicktale: z.string().describe('Clicktale tracking code'),
salesforce: z
.object({
campaign: z.boolean().describe('Indicates whether Salesforce campaign tracking is enabled'),
notes: z.boolean().describe('Indicates whether Salesforce notes tracking is enabled'),
})
.optional()
.describe('Salesforce tracking options'),
capsule: z
.object({
notes: z.boolean().describe('Indicates whether Capsule notes tracking is enabled'),
})
.optional()
.describe('Capsule tracking options'),
})
.passthrough()
const rssOptsSchema = z
.object({
feed_url: z.string().describe('URL of the RSS feed'),
frequency: z.string().describe('Frequency of sending (e.g., "daily")'),
schedule: z
.object({
hour: z.number().describe('Hour of the day for sending'),
daily_send: z
.object({
sunday: z.boolean().title('Sunday').describe('Send on Sunday'),
monday: z.boolean().title('Monday').describe('Send on Monday'),
tuesday: z.boolean().title('Tuesday').describe('Send on Tuesday'),
wednesday: z.boolean().title('Wednesday').describe('Send on Wednesday'),
thursday: z.boolean().title('Thursday').describe('Send on Thursday'),
friday: z.boolean().title('Friday').describe('Send on Friday'),
saturday: z.boolean().title('Saturday').describe('Send on Saturday'),
})
.title('Daily Send')
.describe('Daily send options'),
weekly_send_day: z.string().describe('Day of the week for weekly sending'),
monthly_send_date: z.number().describe('Date of the month for monthly sending'),
})
.title('Schedule')
.describe('RSS feed schedule'),
last_sent: z.string().describe('Last time the RSS feed was sent'),
constrain_rss_img: z.boolean().describe('Indicates whether to constrain RSS images'),
})
.passthrough()
const abSplitOptsSchema = z
.object({
split_test: z.string().describe('Type of split test (e.g., "subject")'),
pick_winner: z.string().describe('Criteria for picking the winner'),
wait_units: z.string().describe('Units for waiting (e.g., "hours")'),
wait_time: z.number().describe('Wait time'),
split_size: z.number().describe('Size of the split'),
from_name_a: z.string().describe("Sender's name for variation A"),
from_name_b: z.string().describe("Sender's name for variation B"),
reply_email_a: z.string().describe('Reply-to email address for variation A'),
reply_email_b: z.string().describe('Reply-to email address for variation B'),
subject_a: z.string().describe('Subject line for variation A'),
subject_b: z.string().describe('Subject line for variation B'),
send_time_a: z.string().describe('Send time for variation A'),
send_time_b: z.string().describe('Send time for variation B'),
send_time_winner: z.string().describe('Send time for the winner'),
})
.passthrough()
const socialCardSchema = z.object({
image_url: z.string().describe('URL of the social card image'),
description: z.string().describe('Description of the social card'),
title: z.string().describe('Title of the social card'),
})
const reportSummarySchema = z
.object({
opens: z.number().describe('Total opens'),
unique_opens: z.number().describe('Unique opens'),
open_rate: z.number().describe('Open rate'),
clicks: z.number().describe('Total clicks'),
subscriber_clicks: z.number().describe('Subscriber clicks'),
click_rate: z.number().describe('Click rate'),
ecommerce: z
.object({
total_orders: z.number().describe('Total ecommerce orders'),
total_spent: z.number().describe('Total amount spent in ecommerce'),
total_revenue: z.number().describe('Total ecommerce revenue'),
})
.title('Ecommerce')
.describe('Ecommerce data for the campaign'),
})
.passthrough()
const deliveryStatusSchema = z.union([
z.object({
enabled: z.literal(true).describe('Indicates whether delivery status tracking is enabled'),
can_cancel: z.boolean().describe('Indicates whether the campaign can be canceled'),
status: z.string().describe('Current delivery status'),
emails_sent: z.number().describe('Total emails sent'),
emails_canceled: z.number().describe('Total emails canceled'),
}),
z.object({ enabled: z.literal(false).describe('Delivery status tracking is disabled') }),
])
export {
HttpMethodSchema,
linkSchema,
tagsSchema,
memberLastNoteSchema,
memberMarketingPermissionsInputSchema,
memberMarketingPermissionsSchema,
memberLocationSchema,
fullMemberLocationSchema,
memberEcommerceDataSchema,
memberStatsSchema,
batchStatusSchema,
constraintsSchema,
abSplitOptsSchema,
rssOptsSchema,
campaignDefaultsSchema,
contactSchema,
deliveryStatusSchema,
recipientsSchema,
settingsSchema,
reportSummarySchema,
socialCardSchema,
statsSchema,
trackingSchema,
variateSettingsSchema,
listSchema,
}
+22
View File
@@ -0,0 +1,22 @@
import * as sdk from '@botpress/sdk'
import * as bp from '.botpress'
export type Handler = bp.IntegrationProps['handler']
export type HandlerProps = bp.HandlerProps
export type IntegrationCtx = bp.Context
export type Logger = bp.Logger
export type Client = bp.Client
export type Conversation = bp.ClientResponses['getConversation']['conversation']
export type Message = bp.ClientResponses['getMessage']['message']
export type User = bp.ClientResponses['getUser']['user']
export type Event = bp.ClientResponses['getEvent']['event']
export type EventDefinition = sdk.EventDefinition
export type ActionDefinition = sdk.ActionDefinition
export type ChannelDefinition = sdk.ChannelDefinition
export type MessageDefinition = sdk.MessageDefinition
export type ActionProps = bp.AnyActionProps
export type MessageHandlerProps = bp.AnyMessageProps
export type AckFunction = bp.AnyAckFunction
+48
View File
@@ -0,0 +1,48 @@
import { RuntimeError, z } from '@botpress/sdk'
import { MailchimpApi } from './client'
import { Customer, MailchimpAPIError } from './misc/custom-types'
import { Logger } from './misc/types'
import * as bp from '.botpress'
export const getMailchimpClient = (config: bp.configuration.Configuration, logger?: Logger) =>
new MailchimpApi(config.apiKey, config.serverPrefix, logger)
export const getValidCustomer = (validatedInput: Customer) => ({
email: validatedInput.email,
firstName: validatedInput.firstName,
lastName: validatedInput.lastName,
company: validatedInput.company,
birthday: validatedInput.birthday,
language: validatedInput.language,
address1: validatedInput.address1,
address2: validatedInput.address2,
city: validatedInput.city,
state: validatedInput.state,
zip: validatedInput.zip,
country: validatedInput.country,
phone: validatedInput.phone,
})
export const isMailchimpError = (error: any): error is MailchimpAPIError => {
return 'status' in error && 'response' in error && 'body' in error.response
}
export const isZodError = (error: any): error is z.ZodError => {
return error && typeof error === 'object' && z.is.zuiError(error) && 'errors' in error
}
export const parseError = (error: any): RuntimeError => {
if (isMailchimpError(error)) {
return new RuntimeError(
`Mailchimp API errored with ${error.response.body.status} status code: ${error.response.body.detail}`,
{
name: error.response.body.title,
message: error.response.body.detail,
}
)
}
if (isZodError(error)) {
return new RuntimeError(`Output does not conform to expected schema: ${error.message}`, error)
}
return new RuntimeError(`Unexpected error: ${error.message}`, error)
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": { "*": ["./*"] },
"outDir": "dist"
},
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
}
+2
View File
@@ -0,0 +1,2 @@
import config from '../../vitest.config'
export default config