chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user